diff --git a/.agents/skills/helm-dev-environment/SKILL.md b/.agents/skills/helm-dev-environment/SKILL.md index 0b5a069eeb..103994af13 100644 --- a/.agents/skills/helm-dev-environment/SKILL.md +++ b/.agents/skills/helm-dev-environment/SKILL.md @@ -319,6 +319,29 @@ above) instead of relying only on `OPENSHELL_GATEWAY_ENDPOINT`; some tests call ### Kubernetes E2E Notes Use `mise run e2e:kubernetes` for the standard Helm-backed Kubernetes suite. +Set `OPENSHELL_E2E_KUBE_TEST=kubernetes_image_update` to scope the Rust suite to +combined image, command, environment, and metadata updates with retained sandbox, +SSH, and PVC identities. The next start verifies the new argv and environment. This target is +included in both existing Agent Sandbox API-version lanes; unlike the Python +Docker suite, it exercises the driver that supports image updates. + +Image updates default to disabled outside test fixtures. The e2e wrapper opts +in only for a fresh gateway fixture that it owns; set +`OPENSHELL_E2E_KUBE_IMAGE_UPDATES=0` to prevent that opt-in. Do not propagate +activation to an existing shared gateway or enable it during a mixed-version +rollout. For manual testing, follow the retirement and activation checklist in +`docs/kubernetes/high-availability.mdx` before setting +`gatewayConfig: {"openshell.gateway": {enable_sandbox_updates: true}}`. +The gateway process equivalents are `--enable-sandbox-updates true` and +`OPENSHELL_ENABLE_SANDBOX_UPDATES=true`. Retire every old process and Job, +settle outstanding driver work, prevent old binaries returning, and do not +treat PostgreSQL connection markers or membership TTL expiry as retirement +proof. SQLite testing supports one gateway process only. Image-update clients +must call the separate `SandboxUpdates` service on the existing gateway +listener, not add a method to the baseline `OpenShell` service or inspect new +public `SandboxStatus` image fields. Pending-operation recovery uses the same +requested image with a fresh version and the documented request-ID semantics. + The kube e2e wrapper creates only one port-forward, to `svc/openshell`; it no longer forwards the unauthenticated health listener or runs a `/readyz` e2e target. `/readyz` remains covered by server unit/integration tests. diff --git a/Cargo.lock b/Cargo.lock index f36af0eb0f..642c045a6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4511,6 +4511,7 @@ dependencies = [ "prost", "prost-types", "rcgen", + "regex", "rustls", "serde", "serde_json", diff --git a/crates/openshell-core/README.md b/crates/openshell-core/README.md index 1cf1b7e70e..a7bd283572 100644 --- a/crates/openshell-core/README.md +++ b/crates/openshell-core/README.md @@ -65,6 +65,27 @@ configuration validation live in `openshell_core::middleware`. Policy admission and the supervisor runtime consume the same contract without introducing a dependency from the policy crate to the supervisor implementation. +## Optional Compute Updates + +The generated `proto::compute::v1::compute_driver_server::ComputeDriver` trait +retains its baseline methods and streaming associated type. External drivers +that support image updates additionally serve `ComputeDriverImageUpdates` on +the same endpoint and advertise `openshell.compute.update-sandbox-image`. +Drivers without that capability need no new generated-trait method. + +Drivers supporting command and environment replacement additionally serve +`ComputeDriverConfigurationUpdates` and advertise +`openshell.compute.update-sandbox-configuration`. Its separate typed request +retains image-update lifecycle preconditions. Both optional in-process methods +have `UNIMPLEMENTED` defaults, so image-only implementations remain valid. + +Existing linked in-process drivers and factories need no new trait or hook. +Only drivers opting into image updates implement +`openshell_core::compute::ComputeDriver` and register that implementation through +`ComputeDriverFactory::build_with_image_updates`. The default factory hook calls +the existing `build` method without an updater. The gateway requires both this +separate registration and the negotiated capability before invoking updates. + ## Protobuf Struct Conversion Use `openshell_core::proto_struct` when crossing between `serde_json` values and diff --git a/crates/openshell-core/src/compute.rs b/crates/openshell-core/src/compute.rs new file mode 100644 index 0000000000..7e3f7046b0 --- /dev/null +++ b/crates/openshell-core/src/compute.rs @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Opt-in in-process sandbox updates, separate from the baseline driver contract. + +use crate::proto::compute::v1::{ + UpdateSandboxImageRequest, UpdateSandboxImageResponse, compute_driver_server, +}; + +/// Optional in-process image updater, registered separately by a driver factory. +/// +/// Existing drivers need only the generated baseline trait, not this extension. +/// Supporting factories opt in through `ComputeDriverFactory::build_with_image_updates` +/// and advertise the image-update capability. External servers use the separate +/// generated `ComputeDriverImageUpdates` or `ComputeDriverConfigurationUpdates` +/// service on the same endpoint instead. Each operation requires its own capability. +#[tonic::async_trait] +pub trait ComputeDriver: compute_driver_server::ComputeDriver { + async fn update_sandbox_configuration( + &self, + _request: tonic::Request, + ) -> Result< + tonic::Response, + tonic::Status, + > { + Err(tonic::Status::unimplemented( + "sandbox configuration updates are not supported by this driver", + )) + } + + async fn update_sandbox_image( + &self, + _request: tonic::Request, + ) -> Result, tonic::Status> { + Err(tonic::Status::unimplemented( + "storage-preserving image updates are not supported by this driver", + )) + } +} diff --git a/crates/openshell-core/src/config.rs b/crates/openshell-core/src/config.rs index 7f5d4d1ec0..ce7b01bc24 100644 --- a/crates/openshell-core/src/config.rs +++ b/crates/openshell-core/src/config.rs @@ -252,6 +252,11 @@ pub struct Config { /// Disabled for local gateways by default. pub enable_websocket_tunnel: bool, + /// Operator activation after all gateways and outstanding legacy driver + /// operations have been drained onto the configuration-update-compatible protocol. + /// Disabled by default; not a license to run mixed-version writers. + pub enable_sandbox_updates: bool, + /// Disabled-by-default gateway interceptor service configs. pub gateway_interceptors: Vec, @@ -936,6 +941,7 @@ impl Config { oidc: None, auth: GatewayAuthConfig::default(), enable_websocket_tunnel: false, + enable_sandbox_updates: false, gateway_interceptors: Vec::new(), provider_profile_sources: vec![GatewayProviderProfileSourceConfig::User], mtls_auth: MtlsAuthConfig::default(), diff --git a/crates/openshell-core/src/extension_protocol.rs b/crates/openshell-core/src/extension_protocol.rs index 9de0b05187..e3c371a2e9 100644 --- a/crates/openshell-core/src/extension_protocol.rs +++ b/crates/openshell-core/src/extension_protocol.rs @@ -22,6 +22,11 @@ pub const COMPUTE_LAUNCH_AUTHENTICATION: &str = "openshell.compute.launch-authen /// Driver stages operator-selected external isolation routes for the supervisor. pub const COMPUTE_ISOLATION_BACKEND: &str = "openshell.compute.isolation-backend"; +/// Optional stopped-state image updates retaining the resource and its storage. +pub const COMPUTE_UPDATE_SANDBOX_CONFIGURATION: &str = + "openshell.compute.update-sandbox-configuration"; +pub const COMPUTE_UPDATE_SANDBOX_IMAGE: &str = "openshell.compute.update-sandbox-image"; + const MAX_IMPLEMENTATION_NAME_BYTES: usize = 128; const MAX_IMPLEMENTATION_VERSION_BYTES: usize = 128; const MAX_CAPABILITY_BYTES: usize = 128; @@ -125,6 +130,8 @@ pub fn gateway_metadata(family: ExtensionFamily) -> PeerMetadata { if family == ExtensionFamily::Compute { supported_capabilities.push(COMPUTE_LAUNCH_AUTHENTICATION.to_string()); supported_capabilities.push(COMPUTE_ISOLATION_BACKEND.to_string()); + supported_capabilities.push(COMPUTE_UPDATE_SANDBOX_IMAGE.to_string()); + supported_capabilities.push(COMPUTE_UPDATE_SANDBOX_CONFIGURATION.to_string()); } PeerMetadata { protocol_version: Some(ProtocolVersion { @@ -396,6 +403,23 @@ fn valid_capability(value: &str) -> bool { mod tests { use super::*; + #[test] + fn compute_gateway_advertises_both_optional_extensions() { + let gateway = gateway_metadata(ExtensionFamily::Compute); + assert!( + gateway + .supported_capabilities + .iter() + .any(|c| c == COMPUTE_ISOLATION_BACKEND) + ); + assert!( + gateway + .supported_capabilities + .iter() + .any(|c| c == COMPUTE_UPDATE_SANDBOX_IMAGE) + ); + } + fn compatible() -> (PeerMetadata, PeerMetadata) { let gateway = gateway_metadata(ExtensionFamily::Compute); let extension = extension_metadata( diff --git a/crates/openshell-core/src/lib.rs b/crates/openshell-core/src/lib.rs index aa7613554d..a56fafe246 100644 --- a/crates/openshell-core/src/lib.rs +++ b/crates/openshell-core/src/lib.rs @@ -11,6 +11,7 @@ pub mod activity; pub mod auth; +pub mod compute; pub mod config; pub mod container_paths; pub mod denial; @@ -57,6 +58,7 @@ pub mod rpc_error; pub mod sandbox_env; pub mod sandbox_generation; pub mod sandbox_session; +pub mod sandbox_update; pub mod secrets; pub mod settings; pub mod shell; diff --git a/crates/openshell-core/src/sandbox_update.rs b/crates/openshell-core/src/sandbox_update.rs new file mode 100644 index 0000000000..8654c391e8 --- /dev/null +++ b/crates/openshell-core/src/sandbox_update.rs @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Shared projections for selected stopped-sandbox configuration changes. +use crate::proto::{Sandbox, SandboxCommandUpdate, SandboxMapUpdate}; +use std::collections::HashMap; + +/// System metadata is never caller-replaceable, including subdomains. +#[must_use] +pub fn is_system_metadata_key(key: &str) -> bool { + key.split_once('/').is_some_and(|(domain, _)| { + domain == "openshell.ai" + || domain.ends_with(".openshell.ai") + || domain == "agents.x-k8s.io" + || domain.ends_with(".agents.x-k8s.io") + }) +} + +fn replace_user_metadata(current: &mut HashMap, update: &SandboxMapUpdate) { + current.retain(|key, _| is_system_metadata_key(key)); + current.extend(update.values.clone()); +} + +/// Caller must validate syntax and forbid system keys before projecting. +pub fn apply_configuration( + sandbox: &mut Sandbox, + command: Option<&SandboxCommandUpdate>, + environment: Option<&SandboxMapUpdate>, + labels: Option<&SandboxMapUpdate>, + annotations: Option<&SandboxMapUpdate>, +) { + if let Some(spec) = sandbox.spec.as_mut() { + if let Some(command) = command { + spec.command.clone_from(&command.argv); + } + if let Some(environment) = environment { + spec.environment.clone_from(&environment.values); + } + } + if let Some(metadata) = sandbox.metadata.as_mut() { + if let Some(labels) = labels { + replace_user_metadata(&mut metadata.labels, labels); + } + if let Some(annotations) = annotations { + replace_user_metadata(&mut metadata.annotations, annotations); + } + } +} diff --git a/crates/openshell-core/tests/compute_driver_compat.rs b/crates/openshell-core/tests/compute_driver_compat.rs new file mode 100644 index 0000000000..40f0124af5 --- /dev/null +++ b/crates/openshell-core/tests/compute_driver_compat.rs @@ -0,0 +1,292 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Fixed baseline server source fixture from 6546691d5e493343c7877573252e835d28bf9e23. +//! It deliberately implements only the original generated `ComputeDriver` methods, +//! including its concrete streaming associated type, with no image-update method +//! or in-process extension trait. New optional RPCs must not break this source. + +use openshell_core::proto::compute::v1::{ + AuthenticateSandboxRequest, AuthenticateSandboxResponse, CreateSandboxRequest, + CreateSandboxResponse, DeleteSandboxRequest, DeleteSandboxResponse, DeleteWorkspaceRequest, + DeleteWorkspaceResponse, EnsureWorkspaceRequest, EnsureWorkspaceResponse, + GetCapabilitiesRequest, GetCapabilitiesResponse, GetSandboxRequest, GetSandboxResponse, + ListSandboxesRequest, ListSandboxesResponse, StartSandboxRequest, StartSandboxResponse, + StopSandboxRequest, StopSandboxResponse, UpdateSandboxImageRequest, UpdateSandboxImageResponse, + ValidateSandboxCreateRequest, ValidateSandboxCreateResponse, WatchSandboxesEvent, + WatchSandboxesRequest, + compute_driver_client::ComputeDriverClient, + compute_driver_image_updates_client::ComputeDriverImageUpdatesClient, + compute_driver_image_updates_server::{ + ComputeDriverImageUpdates, ComputeDriverImageUpdatesServer, + }, + compute_driver_server::{ComputeDriver, ComputeDriverServer}, +}; +use prost::Message; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; +use tokio::net::{TcpListener, TcpStream}; +use tokio_stream::Stream; +use tonic::{Request, Response, Status}; + +#[derive(Debug)] +struct BaselineDriver; + +#[tonic::async_trait] +impl ComputeDriver for BaselineDriver { + async fn get_capabilities( + &self, + _request: Request, + ) -> Result, Status> { + Ok(Response::new(GetCapabilitiesResponse { + driver_name: "baseline-source-fixture".into(), + ..Default::default() + })) + } + + async fn authenticate_sandbox( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("fixture")) + } + + async fn validate_sandbox_create( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("fixture")) + } + + async fn get_sandbox( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("fixture")) + } + + async fn list_sandboxes( + &self, + _request: Request, + ) -> Result, Status> { + Ok(Response::new(ListSandboxesResponse { sandboxes: vec![] })) + } + + async fn create_sandbox( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("fixture")) + } + + async fn stop_sandbox( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("fixture")) + } + + async fn start_sandbox( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("fixture")) + } + + async fn delete_sandbox( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("fixture")) + } + + type WatchSandboxesStream = tokio_stream::Empty>; + + async fn watch_sandboxes( + &self, + _request: Request, + ) -> Result, Status> { + Ok(Response::new(tokio_stream::empty())) + } + + async fn ensure_workspace( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("fixture")) + } + + async fn delete_workspace( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("fixture")) + } +} + +#[derive(Debug, Clone, Default)] +struct ImageUpdates { + calls: Arc>>, +} + +#[tonic::async_trait] +impl ComputeDriverImageUpdates for ImageUpdates { + async fn update_sandbox_image( + &self, + request: Request, + ) -> Result, Status> { + self.calls.lock().unwrap().push(request.into_inner()); + Ok(Response::new(UpdateSandboxImageResponse {})) + } +} + +struct TcpIncoming(TcpListener); + +impl Stream for TcpIncoming { + type Item = std::io::Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.0.poll_accept(cx).map(|result| { + Some(result.map(|(stream, _)| { + openshell_core::net::set_tcp_nodelay_best_effort(&stream); + stream + })) + }) + } +} + +async fn roundtrip(serve_image_updates: bool) { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let address = listener.local_addr().unwrap(); + let extension = ImageUpdates::default(); + let optional_service = + serve_image_updates.then(|| ComputeDriverImageUpdatesServer::new(extension.clone())); + let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel(); + let server = tokio::spawn(async move { + tonic::transport::Server::builder() + .add_service(ComputeDriverServer::new(BaselineDriver)) + .add_optional_service(optional_service) + .serve_with_incoming_shutdown(TcpIncoming(listener), async { + let _ = shutdown_rx.await; + }) + .await + .unwrap(); + }); + let channel = tonic::transport::Endpoint::from_shared(format!("http://{address}")) + .unwrap() + .connect() + .await + .unwrap(); + let mut baseline = ComputeDriverClient::new(channel.clone()); + assert_eq!( + baseline + .get_capabilities(GetCapabilitiesRequest::default()) + .await + .unwrap() + .into_inner() + .driver_name, + "baseline-source-fixture" + ); + assert!( + baseline + .list_sandboxes(ListSandboxesRequest {}) + .await + .unwrap() + .into_inner() + .sandboxes + .is_empty() + ); + assert!( + baseline + .watch_sandboxes(WatchSandboxesRequest {}) + .await + .unwrap() + .into_inner() + .message() + .await + .unwrap() + .is_none() + ); + + let request = UpdateSandboxImageRequest { + sandbox_id: "sandbox-id".into(), + image: "example/workload:v2".into(), + ..Default::default() + }; + let result = ComputeDriverImageUpdatesClient::new(channel) + .update_sandbox_image(request.clone()) + .await; + if serve_image_updates { + result.unwrap(); + assert_eq!(*extension.calls.lock().unwrap(), vec![request]); + } else { + assert_eq!(result.unwrap_err().code(), tonic::Code::Unimplemented); + assert!(extension.calls.lock().unwrap().is_empty()); + } + // Absence of the optional service must not impair the original service. + baseline + .list_sandboxes(ListSandboxesRequest {}) + .await + .unwrap(); + drop(baseline); + shutdown_tx.send(()).unwrap(); + server.await.unwrap(); +} + +#[tokio::test] +async fn baseline_generated_server_source_and_transport_remain_compatible() { + roundtrip(false).await; +} + +#[tokio::test] +async fn optional_image_service_shares_the_baseline_channel() { + roundtrip(true).await; +} + +#[test] +fn baseline_compute_service_methods_are_frozen() { + let descriptor = + prost_types::FileDescriptorSet::decode(openshell_core::FILE_DESCRIPTOR_SET).unwrap(); + let compute = descriptor + .file + .iter() + .find(|file| file.package.as_deref() == Some("openshell.compute.v1")) + .unwrap(); + let baseline = compute + .service + .iter() + .find(|service| service.name.as_deref() == Some("ComputeDriver")) + .unwrap(); + assert_eq!( + baseline + .method + .iter() + .map(|method| method.name.as_deref().unwrap()) + .collect::>(), + vec![ + "GetCapabilities", + "AuthenticateSandbox", + "ValidateSandboxCreate", + "GetSandbox", + "ListSandboxes", + "CreateSandbox", + "StopSandbox", + "StartSandbox", + "DeleteSandbox", + "WatchSandboxes", + "EnsureWorkspace", + "DeleteWorkspace", + ] + ); + let image_updates = compute + .service + .iter() + .find(|service| service.name.as_deref() == Some("ComputeDriverImageUpdates")) + .unwrap(); + assert_eq!(image_updates.method.len(), 1); + assert_eq!( + image_updates.method[0].name.as_deref(), + Some("UpdateSandboxImage") + ); +} diff --git a/crates/openshell-driver-docker/src/lib.rs b/crates/openshell-driver-docker/src/lib.rs index c306377141..5b2bbd2bda 100644 --- a/crates/openshell-driver-docker/src/lib.rs +++ b/crates/openshell-driver-docker/src/lib.rs @@ -3115,6 +3115,9 @@ fn validate_docker_outer_fence( } Ok(()) } +#[tonic::async_trait] +impl openshell_core::compute::ComputeDriver for ComputeDriverService {} + #[tonic::async_trait] impl ComputeDriver for ComputeDriverService { type WatchSandboxesStream = WatchStream; @@ -3268,6 +3271,9 @@ impl ComputeDriver for ComputeDriverService { } } +#[tonic::async_trait] +impl openshell_core::compute::ComputeDriver for DockerComputeDriver {} + #[tonic::async_trait] impl ComputeDriver for DockerComputeDriver { async fn authenticate_sandbox( diff --git a/crates/openshell-driver-kubernetes/Cargo.toml b/crates/openshell-driver-kubernetes/Cargo.toml index f1d55cc0f0..738d2c9bc5 100644 --- a/crates/openshell-driver-kubernetes/Cargo.toml +++ b/crates/openshell-driver-kubernetes/Cargo.toml @@ -42,6 +42,7 @@ tracing-subscriber = { workspace = true } thiserror = { workspace = true } miette = { workspace = true } notify = "8" +regex = { workspace = true } rcgen = { workspace = true } bytes = { workspace = true } http = { workspace = true } diff --git a/crates/openshell-driver-kubernetes/README.md b/crates/openshell-driver-kubernetes/README.md index 7dc2478d34..4a845f56f1 100644 --- a/crates/openshell-driver-kubernetes/README.md +++ b/crates/openshell-driver-kubernetes/README.md @@ -299,6 +299,37 @@ storage. The driver confirms the stop from both the published `Suspended` condition and deletion of the backing pod. Legacy `v1alpha1` controllers omit a usable stopped condition, so pod deletion alone confirms their stop. +The optional `openshell.compute.update-sandbox-image` capability supports +image-only updates while the sandbox is stopped. External drivers serve it as +the separate `ComputeDriverImageUpdates` service on the baseline `ComputeDriver` +endpoint, preserving the baseline generated server trait. The linked gateway +factory opts in through `ComputeDriverFactory::build_with_image_updates` without +changing the baseline driver object. `UpdateSandboxImage` requires +the persisted Kubernetes runtime identity, current image, sandbox name, and a +stable operation ID with a monotonic image revision. The driver verifies that +the same Sandbox resource is suspended, runtime bootstrap has completed, and +no workload or supervisor Pods +remain, then revalidates stored external-resource admission. It patches only +the agent image, the workspace seed image, and dedicated operation evidence, +using the resource UID and `resourceVersion` as concurrency guards. A repeat of +the same completed operation succeeds without a second patch; a different +current image, stale revision, or reused operation ID with different parameters +fails closed. A stale operation cannot reapply an image after a later update +returns to an earlier tag. Image updates do not delete or recreate the Sandbox resource, PVCs, mounts, +trusted runtime images, or bootstrap annotations. They leave the sandbox +stopped. Existing workspace contents and the seed sentinel remain unchanged, +so an image update does not replace files already stored under `/sandbox`. + +The additional `openshell.compute.update-sandbox-configuration` capability and +`ComputeDriverConfigurationUpdates` service reconcile workload argv and effective +user environment with the image in one UID/resource-version-fenced patch. The +main-process annotation and agent environment supply the next runtime generation; +the trusted container launcher and runtime environment remain intact. Retry +records identify selected fields and verify their live target values without +copying environment values into operation evidence. Top-level public sandbox +labels and annotations remain gateway metadata; this operation does not change +Kubernetes pod metadata. + The workspace PVC size defaults to `workspace_default_storage_size`. Set `workspace_storage_class` to pin the PVC to a specific `StorageClass`; an empty value omits `storageClassName` so the cluster's default `StorageClass` applies. diff --git a/crates/openshell-driver-kubernetes/src/driver.rs b/crates/openshell-driver-kubernetes/src/driver.rs index 91a1ff416b..bee605d712 100644 --- a/crates/openshell-driver-kubernetes/src/driver.rs +++ b/crates/openshell-driver-kubernetes/src/driver.rs @@ -88,6 +88,9 @@ use tokio::sync::{OnceCell, mpsc}; use tokio_stream::wrappers::ReceiverStream; use tracing::{debug, info, warn}; +#[path = "image_update.rs"] +mod image_update; + pub type WatchStream = Pin> + Send>>; @@ -1132,7 +1135,11 @@ impl KubernetesComputeDriver { openshell_core::extension_protocol::ExtensionFamily::Compute, "openshell/kubernetes", openshell_core::VERSION, - [], + [ + openshell_core::extension_protocol::COMPUTE_UPDATE_SANDBOX_IMAGE.to_string(), + openshell_core::extension_protocol::COMPUTE_UPDATE_SANDBOX_CONFIGURATION + .to_string(), + ], )), }) } diff --git a/crates/openshell-driver-kubernetes/src/grpc.rs b/crates/openshell-driver-kubernetes/src/grpc.rs index 86b0bf4f6b..8c24ddfe82 100644 --- a/crates/openshell-driver-kubernetes/src/grpc.rs +++ b/crates/openshell-driver-kubernetes/src/grpc.rs @@ -328,6 +328,64 @@ impl ComputeDriver for ComputeDriverService { } } +#[tonic::async_trait] +impl openshell_core::compute::ComputeDriver for ComputeDriverService { + async fn update_sandbox_configuration( + &self, + request: Request, + ) -> Result< + Response, + Status, + > { + self.driver + .update_sandbox_configuration(&request.into_inner()) + .await + .map_err(|error| Status::from(openshell_core::ComputeDriverError::from(error)))?; + Ok(Response::new( + openshell_core::proto::compute::v1::UpdateSandboxConfigurationResponse {}, + )) + } + + async fn update_sandbox_image( + &self, + request: Request, + ) -> Result, Status> + { + self.rpc_tracer + .trace(openshell_otel::rpc::UPDATE_SANDBOX_IMAGE, async { + self.driver + .update_sandbox_image(&request.into_inner()) + .await + .map_err(|error| { + Status::from(openshell_core::ComputeDriverError::from(error)) + })?; + Ok(Response::new( + openshell_core::proto::compute::v1::UpdateSandboxImageResponse {}, + )) + }) + .await + } +} + +#[tonic::async_trait] +impl openshell_core::proto::compute::v1::compute_driver_image_updates_server::ComputeDriverImageUpdates + for ComputeDriverService +{ + async fn update_sandbox_image( + &self, + request: Request, + ) -> Result, Status> { + openshell_core::compute::ComputeDriver::update_sandbox_image(self, request).await + } +} + +#[tonic::async_trait] +impl openshell_core::proto::compute::v1::compute_driver_configuration_updates_server::ComputeDriverConfigurationUpdates for ComputeDriverService { + async fn update_sandbox_configuration(&self, request: Request) -> Result, Status> { + openshell_core::compute::ComputeDriver::update_sandbox_configuration(self, request).await + } +} + fn workspace_delete_requires_namespace_access(mode: WorkspaceMode) -> bool { matches!(mode, WorkspaceMode::Managed) } diff --git a/crates/openshell-driver-kubernetes/src/image_update.rs b/crates/openshell-driver-kubernetes/src/image_update.rs new file mode 100644 index 0000000000..293bc45a39 --- /dev/null +++ b/crates/openshell-driver-kubernetes/src/image_update.rs @@ -0,0 +1,612 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Storage-preserving, stopped-only workload image mutation. + +use super::{ + KUBE_API_TIMEOUT, KubernetesComputeDriver, KubernetesDriverError, LABEL_SANDBOX_ID, + LABEL_SANDBOX_NAME, SANDBOX_POD_NAME_ANNOTATION, SANDBOX_VERSION_V1BETA1, + WORKSPACE_INIT_CONTAINER_NAME, accepts_auth_namespace, admission_error, annotation_or_label, + is_kube_resource_version_conflict, parse_kubernetes_runtime_identity, sandbox_id_from_object, + sandbox_runtime_bootstrap_in_progress, select_expected_sandbox_runtime, +}; +use k8s_openapi::api::core::v1::Pod; +use kube::Api; +use kube::api::{DynamicObject, ListParams, Patch, PatchParams}; +use openshell_core::proto::compute::v1::UpdateSandboxImageRequest; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use std::sync::LazyLock; + +// Deliberately separate from the runtime bootstrap operation annotations. Those +// annotations drive reconciliation and only describe create/start/stop. +const IMAGE_UPDATE_ANNOTATION: &str = "openshell.ai/sandbox-image-update"; + +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] +struct ImageUpdateRecord { + operation_id: String, + expected_image: String, + image: String, + revision: u64, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + configuration: bool, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + command_updated: bool, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + environment_updated: bool, +} + +impl KubernetesComputeDriver { + /// Update only a suspended Sandbox's workload and workspace seed images. + /// The resource, claim templates, existing claims, and runtime bootstrap + /// annotations are never replaced. A retry is recognized from durable + /// operation evidence on the same immutable Sandbox resource. + #[tracing::instrument( + name = "kubernetes.update_sandbox_image", + skip_all, + fields( + otel.name = "kubernetes.update_sandbox_image", + otel.status_code = tracing::field::Empty, + sandbox.id = %request.sandbox_id, + ) + )] + pub async fn update_sandbox_image( + &self, + request: &UpdateSandboxImageRequest, + ) -> Result<(), KubernetesDriverError> { + self.update_workload(request, None).await + } + + pub async fn update_sandbox_configuration( + &self, + request: &openshell_core::proto::compute::v1::UpdateSandboxConfigurationRequest, + ) -> Result<(), KubernetesDriverError> { + let image = request.image_update.as_ref().ok_or_else(|| { + KubernetesDriverError::InvalidArgument("image_update is required".into()) + })?; + validate_configuration(request)?; + self.update_workload(image, Some(request)).await + } + + async fn update_workload( + &self, + request: &UpdateSandboxImageRequest, + configuration: Option< + &openshell_core::proto::compute::v1::UpdateSandboxConfigurationRequest, + >, + ) -> Result<(), KubernetesDriverError> { + let span_status = openshell_otel::ErrorStatusGuard::current(); + validate_image_update_request(request)?; + let _guard = self + .lifecycle_gates + .gate_for(&request.sandbox_id) + .lock_owned() + .await; + // Include discovery, admission and Pod pagination in a single bound. + let result = tokio::time::timeout( + KUBE_API_TIMEOUT, + self.update_sandbox_image_inner(request, configuration), + ) + .await + .unwrap_or_else(|_| { + Err(KubernetesDriverError::Message( + "timed out updating Kubernetes sandbox image; retry the same operation".into(), + )) + }); + span_status.finish(result) + } + + async fn update_sandbox_image_inner( + &self, + request: &UpdateSandboxImageRequest, + configuration: Option< + &openshell_core::proto::compute::v1::UpdateSandboxConfigurationRequest, + >, + ) -> Result<(), KubernetesDriverError> { + let expected = parse_kubernetes_runtime_identity(&request.expected_runtime_identity)?; + self.wait_for_operator_allowlist() + .await + .map_err(admission_error)?; + if !accepts_auth_namespace( + &self.config, + self.operator_allowlist.as_ref(), + &expected.namespace, + ) { + return Err(KubernetesDriverError::Precondition( + "persisted runtime namespace is outside the driver's configured scope".into(), + )); + } + let lookup = self + .supported_sandbox_api_for_lookup(self.client.clone()) + .await + .map_err(KubernetesDriverError::Message)?; + let objects = lookup + .api + .list(&ListParams::default().labels(&self.sandbox_lookup_selector(&request.sandbox_id))) + .await + .map_err(KubernetesDriverError::from_kube)? + .items; + let selected = select_expected_sandbox_runtime(objects, &expected)?; + let name = selected.metadata.name.as_deref().ok_or_else(|| { + KubernetesDriverError::Precondition("Sandbox resource has no name".into()) + })?; + let api = Self::agent_sandbox_api( + self.client.clone(), + &lookup.resource.version, + &expected.namespace, + ); + // Refresh after selection, then use exactly this snapshot's version in + // the patch. Never retry a resourceVersion conflict with a stale image, + // attachment inventory or operating-state decision. + let object = api + .api + .get(name) + .await + .map_err(KubernetesDriverError::from_kube)?; + let object = select_expected_sandbox_runtime(vec![object], &expected)?; + if sandbox_id_from_object(&object).as_deref() != Ok(request.sandbox_id.as_str()) + || annotation_or_label(&object, LABEL_SANDBOX_NAME).as_deref() + != Some(request.name.as_str()) + { + return Err(KubernetesDriverError::Precondition( + "Sandbox resource does not match the requested sandbox identity".into(), + )); + } + require_stopped_for_image_update(&object, &lookup.resource.version)?; + let mut candidate = object.clone(); + let repeat = prepare_update(&mut candidate, request, configuration)?; + if let Some(configuration) = configuration { + prepare_configuration(&mut candidate, configuration, repeat)?; + } + // Revalidate the saved provenance and attachment UIDs against current + // operator policy, even for retries. Do not manufacture new provenance + // or re-render driver_config from a caller's request. + self.admit_stored_resources(&candidate) + .await + .map_err(admission_error)?; + self.require_image_update_pods_absent(&object, &request.sandbox_id, &expected.namespace) + .await?; + if request.validate_only || repeat { + return Ok(()); + } + let version = object + .metadata + .resource_version + .as_deref() + .filter(|version| !version.is_empty()) + .ok_or_else(|| { + KubernetesDriverError::Precondition( + "Sandbox resource has no resourceVersion".into(), + ) + })?; + let record = candidate + .metadata + .annotations + .as_ref() + .and_then(|annotations| annotations.get(IMAGE_UPDATE_ANNOTATION)) + .expect("prepared image update has operation evidence"); + let mut patch = serde_json::json!({ + "metadata": { + "uid": expected.resource_uid, + "resourceVersion": version, + "annotations": {IMAGE_UPDATE_ANNOTATION: record} + }, + "spec": {"podTemplate": {"spec": { + "containers": candidate.data["spec"]["podTemplate"]["spec"]["containers"] + }}} + }); + if configuration.is_some_and(|configuration| configuration.main_process.is_some()) { + let key = super::ANNOTATION_SANDBOX_RUNTIME_MAIN_PROCESS_SPEC; + patch["metadata"]["annotations"][key] = serde_json::json!( + candidate + .metadata + .annotations + .as_ref() + .and_then(|annotations| annotations.get(key)) + ); + } + if let Some(init) = candidate + .data + .pointer("/spec/podTemplate/spec/initContainers") + { + patch["spec"]["podTemplate"]["spec"]["initContainers"] = init.clone(); + } + api.api + .patch(name, &PatchParams::default(), &Patch::Merge(&patch)) + .await + .map_err(|error| { + if is_kube_resource_version_conflict(&error) { + KubernetesDriverError::Precondition( + "Sandbox changed during image update; retry the same operation".into(), + ) + } else { + KubernetesDriverError::from_kube(error) + } + })?; + Ok(()) + } + + async fn require_image_update_pods_absent( + &self, + object: &DynamicObject, + sandbox_id: &str, + namespace: &str, + ) -> Result<(), KubernetesDriverError> { + let pods = Api::::namespaced(self.client.clone(), namespace); + // The controller's workload Pod need not carry the OpenShell sandbox + // label. Inspect owner UIDs and recorded names as well as labels and + // annotations, and reject terminating Pods too. Listing the namespace + // also catches supervisors from earlier runtime generations. + let mut params = ListParams::default().limit(500); + loop { + let page = pods + .list(¶ms) + .await + .map_err(KubernetesDriverError::from_kube)?; + if page + .items + .iter() + .any(|pod| image_update_pod_matches(pod, object, sandbox_id)) + { + return Err(KubernetesDriverError::Precondition( + "cannot update image while sandbox workload or supervisor Pods still exist" + .into(), + )); + } + match page.metadata.continue_.filter(|token| !token.is_empty()) { + Some(token) => params = params.continue_token(&token), + None => return Ok(()), + } + } + } +} + +fn validate_image_update_request( + request: &UpdateSandboxImageRequest, +) -> Result<(), KubernetesDriverError> { + for (field, value) in [ + ("sandbox_id", request.sandbox_id.as_str()), + ("name", request.name.as_str()), + ("operation_id", request.operation_id.as_str()), + ] { + if value.is_empty() + || value.len() > 1024 + || value.trim() != value + || value.chars().any(char::is_control) + { + return Err(KubernetesDriverError::InvalidArgument(format!( + "{field} must be a nonempty bounded identifier" + ))); + } + } + if request.revision == 0 { + return Err(KubernetesDriverError::InvalidArgument( + "image update revision must be nonzero".into(), + )); + } + validate_workload_image(&request.image)?; + // The old reference is only a compare-and-swap precondition, not a new + // launch input. Legacy create accepted references that kubelet cannot pull; + // validating their syntax here would prevent repairing those sandboxes. + if request.expected_image.is_empty() || request.expected_image.len() > 2048 { + return Err(KubernetesDriverError::InvalidArgument( + "expected_image must be a nonempty bounded reference".into(), + )); + } + Ok(()) +} + +fn validate_workload_image(image: &str) -> Result<(), KubernetesDriverError> { + // Distribution reference grammar: registry (including a port or bracketed + // IPv6), lowercase repository path, optional tag and/or algorithm:digest. + // Validation is syntax only: resolving/pulling the image remains kubelet's + // job, using the existing operator-selected image-pull Secret inventory. + static REFERENCE: LazyLock = LazyLock::new(|| { + let host_component = r"[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?"; + let host = format!(r"(?:{host_component}(?:\.{host_component})*|\[[a-fA-F0-9:]+\])"); + let path = r"[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*"; + regex::Regex::new(&format!( + r"^(?:{host}(?::[0-9]+)?/)?{path}(?:/{path})*(?::[a-zA-Z0-9_][a-zA-Z0-9_.-]{{0,127}})?(?:@[a-zA-Z][a-zA-Z0-9]*(?:[-_+.][a-zA-Z][a-zA-Z0-9]*)*:[a-fA-F0-9]{{32,}})?$" + )).expect("valid distribution reference grammar") + }); + let named = image.split('@').next().unwrap_or_default(); + let repository = + named.rsplit_once(':').map_or( + named, + |(prefix, suffix)| { + if suffix.contains('/') { named } else { prefix } + }, + ); + let digest_length_valid = image.rsplit_once('@').is_none_or(|(_, digest)| { + digest + .split_once(':') + .is_some_and(|(algorithm, encoded)| match algorithm { + "sha256" => encoded.len() == 64, + "sha384" => encoded.len() == 96, + "sha512" => encoded.len() == 128, + _ => true, + }) + }); + if image.len() > 2048 + || repository.len() > 255 + || !digest_length_valid + || !REFERENCE.is_match(image) + { + return Err(KubernetesDriverError::InvalidArgument( + "image must be a valid container image reference of at most 2048 bytes".into(), + )); + } + Ok(()) +} + +fn require_stopped_for_image_update( + object: &DynamicObject, + version: &str, +) -> Result<(), KubernetesDriverError> { + let suspended = if version == SANDBOX_VERSION_V1BETA1 { + object + .data + .pointer("/spec/operatingMode") + .and_then(serde_json::Value::as_str) + == Some("Suspended") + } else { + object + .data + .pointer("/spec/replicas") + .and_then(serde_json::Value::as_i64) + == Some(0) + }; + if !suspended + || object.metadata.deletion_timestamp.is_some() + || sandbox_runtime_bootstrap_in_progress(object) + { + return Err(KubernetesDriverError::Precondition( + "image updates require a stopped, suspended sandbox with no runtime bootstrap in progress".into(), + )); + } + Ok(()) +} + +/// Apply the image-only mutation in memory; return true for an exact retry. +fn prepare_update( + object: &mut DynamicObject, + request: &UpdateSandboxImageRequest, + configuration: Option<&openshell_core::proto::compute::v1::UpdateSandboxConfigurationRequest>, +) -> Result { + let evidence = object + .metadata + .annotations + .as_ref() + .and_then(|annotations| annotations.get(IMAGE_UPDATE_ANNOTATION)) + .map(|value| serde_json::from_str::(value)) + .transpose() + .map_err(|_| { + KubernetesDriverError::Precondition("invalid stored image update evidence".into()) + })?; + let record = ImageUpdateRecord { + operation_id: request.operation_id.clone(), + expected_image: request.expected_image.clone(), + image: request.image.clone(), + revision: request.revision, + configuration: configuration.is_some(), + command_updated: configuration.is_some_and(|value| value.main_process.is_some()), + environment_updated: configuration.is_some_and(|value| value.environment.is_some()), + }; + if let Some(saved) = &evidence { + if request.revision < saved.revision { + return Err(KubernetesDriverError::Precondition( + "image update revision is older than the stored image revision".into(), + )); + } + if request.revision == saved.revision && saved != &record { + return Err(KubernetesDriverError::Precondition( + "image update revision was already used with different operation parameters".into(), + )); + } + if saved.operation_id == request.operation_id && saved != &record { + return Err(KubernetesDriverError::Precondition( + "image update operation_id was already used with different parameters".into(), + )); + } + } + let repeat = evidence.as_ref() == Some(&record); + let containers = object + .data + .pointer_mut("/spec/podTemplate/spec/containers") + .and_then(serde_json::Value::as_array_mut) + .ok_or_else(|| { + KubernetesDriverError::Precondition("Sandbox has no workload containers".into()) + })?; + let mut agents = containers + .iter_mut() + .filter(|container| container["name"] == "agent"); + let agent = agents.next().ok_or_else(|| { + KubernetesDriverError::Precondition("Sandbox has no agent container".into()) + })?; + if agents.next().is_some() { + return Err(KubernetesDriverError::Precondition( + "Sandbox has multiple agent containers".into(), + )); + } + let expected: &str = if repeat { + &request.image + } else { + &request.expected_image + }; + if agent["image"].as_str() != Some(expected) { + return Err(KubernetesDriverError::Precondition( + "Sandbox workload image does not match expected_image or the recorded operation target" + .into(), + )); + } + agent["image"] = serde_json::json!(request.image); + if let Some(init) = object + .data + .pointer_mut("/spec/podTemplate/spec/initContainers") + .and_then(serde_json::Value::as_array_mut) + { + for container in init + .iter_mut() + .filter(|container| container["name"] == WORKSPACE_INIT_CONTAINER_NAME) + { + if container["image"].as_str() != Some(expected) { + return Err(KubernetesDriverError::Precondition( + "Sandbox workspace seed image does not match its workload image".into(), + )); + } + container["image"] = serde_json::json!(request.image); + } + } + object + .metadata + .annotations + .get_or_insert_with(BTreeMap::new) + .insert( + IMAGE_UPDATE_ANNOTATION.into(), + serde_json::to_string(&record).expect("image update record serializes"), + ); + Ok(repeat) +} + +fn validate_configuration( + request: &openshell_core::proto::compute::v1::UpdateSandboxConfigurationRequest, +) -> Result<(), KubernetesDriverError> { + let invalid = |message: &str| KubernetesDriverError::InvalidArgument(message.into()); + if let Some(spec) = &request.main_process + && (spec.command.len() > 256 + || spec.command.iter().map(String::len).sum::() > 256 * 1024 + || spec.command.first().is_some_and(String::is_empty) + || spec.command.iter().any(|arg| arg.contains('\0'))) + { + return Err(invalid("invalid workload argv")); + } + if let Some(environment) = &request.environment { + if environment.values.len() > 256 + || environment + .values + .iter() + .map(|(key, value)| key.len() + value.len()) + .sum::() + > 256 * 1024 + { + return Err(invalid("environment exceeds size limits")); + } + for (key, value) in &environment.values { + if key.is_empty() + || key.starts_with("OPENSHELL_") + || key.as_bytes()[0].is_ascii_digit() + || !key.bytes().all(|c| c.is_ascii_alphanumeric() || c == b'_') + || value.contains(['\0', '\n', '\r']) + { + return Err(invalid("invalid or reserved workload environment entry")); + } + } + } + Ok(()) +} + +fn prepare_configuration( + object: &mut DynamicObject, + request: &openshell_core::proto::compute::v1::UpdateSandboxConfigurationRequest, + repeat: bool, +) -> Result<(), KubernetesDriverError> { + if let Some(spec) = &request.main_process { + let key = super::ANNOTATION_SANDBOX_RUNTIME_MAIN_PROCESS_SPEC; + let encoded = super::required_sandbox_annotation(object, key)?; + let mut main = openshell_core::sandbox_env::MainProcessConfig::decode(&encoded) + .map_err(KubernetesDriverError::Precondition)?; + let original = main.clone(); + main.command.clone_from(&spec.command); + main.tty = spec.command.is_empty() || spec.tty; + if repeat && main != original { + return Err(KubernetesDriverError::Precondition( + "recorded update command does not match target".into(), + )); + } + object + .metadata + .annotations + .get_or_insert_with(BTreeMap::new) + .insert( + key.into(), + serde_json::to_string(&main).map_err(|_| { + KubernetesDriverError::InvalidArgument("cannot encode main process".into()) + })?, + ); + } + if let Some(environment) = &request.environment { + let containers = object + .data + .pointer_mut("/spec/podTemplate/spec/containers") + .and_then(serde_json::Value::as_array_mut) + .ok_or_else(|| { + KubernetesDriverError::Precondition("missing workload containers".into()) + })?; + let agent = containers + .iter_mut() + .find(|container| container["name"] == "agent") + .ok_or_else(|| KubernetesDriverError::Precondition("missing agent container".into()))?; + let mut current = std::collections::HashMap::new(); + let mut retained = Vec::new(); + for entry in agent["env"].as_array().into_iter().flatten() { + let name = entry["name"].as_str().ok_or_else(|| { + KubernetesDriverError::Precondition("invalid workload environment".into()) + })?; + if name.starts_with("OPENSHELL_") { + retained.push(entry.clone()); + } else if let Some(value) = entry["value"].as_str() { + current.insert(name.to_owned(), value.to_owned()); + } else { + return Err(KubernetesDriverError::Precondition( + "cannot replace workload environment with external value sources".into(), + )); + } + } + if repeat && current != environment.values { + return Err(KubernetesDriverError::Precondition( + "recorded update environment does not match target".into(), + )); + } + let mut entries: Vec<_> = environment.values.iter().collect(); + entries.sort_unstable_by_key(|(name, _)| *name); + for (name, value) in entries { + retained.push(serde_json::json!({"name": name, "value": value})); + } + agent["env"] = serde_json::json!(retained); + } + Ok(()) +} + +fn image_update_pod_matches(pod: &Pod, object: &DynamicObject, sandbox_id: &str) -> bool { + let metadata = &pod.metadata; + metadata.owner_references.as_ref().is_some_and(|owners| { + owners + .iter() + .any(|owner| Some(owner.uid.as_str()) == object.metadata.uid.as_deref()) + }) || metadata + .labels + .as_ref() + .and_then(|labels| labels.get(LABEL_SANDBOX_ID)) + .map(String::as_str) + == Some(sandbox_id) + || metadata + .annotations + .as_ref() + .and_then(|annotations| annotations.get(LABEL_SANDBOX_ID)) + .map(String::as_str) + == Some(sandbox_id) + || metadata.name.as_deref().is_some_and(|name| { + Some(name) == object.metadata.name.as_deref() + || object + .metadata + .annotations + .as_ref() + .and_then(|annotations| annotations.get(SANDBOX_POD_NAME_ANNOTATION)) + .map(String::as_str) + == Some(name) + }) +} + +#[cfg(test)] +#[path = "image_update_tests.rs"] +mod tests; diff --git a/crates/openshell-driver-kubernetes/src/image_update_tests.rs b/crates/openshell-driver-kubernetes/src/image_update_tests.rs new file mode 100644 index 0000000000..860cdf3c4c --- /dev/null +++ b/crates/openshell-driver-kubernetes/src/image_update_tests.rs @@ -0,0 +1,803 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +use super::super::{ + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_OPERATION, ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_PHASE, + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAPPING, ANNOTATION_SANDBOX_RUNTIME_LOG_LEVEL, + ANNOTATION_SANDBOX_RUNTIME_MAIN_PROCESS_SPEC, ANNOTATION_SANDBOX_RUNTIME_READINESS, + KubernetesComputeConfig, LABEL_SANDBOX_WORKSPACE, OperatorNamespaceAllowlist, + SANDBOX_BOOTSTRAP_CONTAINER_NAME, SANDBOX_BOOTSTRAP_VOLUME_NAME, SANDBOX_GROUP, + SANDBOX_VERSION_V1ALPHA1, WORKSPACE_INIT_MOUNT_PATH, WorkspaceMode, +}; +use super::*; +use http_body_util::BodyExt as _; +use kube::Client; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use tokio::sync::OnceCell; + +struct ApiState { + sandbox: serde_json::Value, + pvc: serde_json::Value, + pods: Vec, + calls: Vec<(http::Method, String)>, + patches: Vec, + conflict: bool, + pods_continue: bool, +} + +fn response( + status: http::StatusCode, + value: serde_json::Value, +) -> http::Response { + http::Response::builder() + .status(status) + .header(http::header::CONTENT_TYPE, "application/json") + .body(kube::client::Body::from(value.to_string().into_bytes())) + .unwrap() +} + +fn merge(target: &mut serde_json::Value, patch: &serde_json::Value) { + if let Some(patch) = patch.as_object() { + for (key, value) in patch { + if value.is_null() { + target.as_object_mut().unwrap().remove(key); + } else { + if target.get(key).is_none() { + target[key] = serde_json::Value::Null; + } + if value.is_object() && !target[key].is_object() { + target[key] = serde_json::json!({}); + } + merge(&mut target[key], value); + } + } + } else { + *target = patch.clone(); + } +} + +fn snapshot(version: &str) -> serde_json::Value { + let mut value = serde_json::json!({ + "apiVersion": format!("{SANDBOX_GROUP}/{version}"), + "kind": "Sandbox", + "metadata": { + "name": "sandbox-cr", "namespace": "openshell", "uid": "resource-uid", + "resourceVersion": "42", "generation": 7, + "labels": {LABEL_SANDBOX_ID: "sandbox-1", LABEL_SANDBOX_NAME: "sandbox-name", LABEL_SANDBOX_WORKSPACE: "team-a"}, + "annotations": { + LABEL_SANDBOX_ID: "sandbox-1", LABEL_SANDBOX_NAME: "sandbox-name", + crate::resource_admission::CONFIG_USED: "true", + crate::resource_admission::IDENTITIES: "{\"PersistentVolumeClaim/openshell/team-data\":\"data-pvc-uid\"}", + "operator.example/bootstrap-provenance": "preserve-me", + ANNOTATION_SANDBOX_RUNTIME_MAIN_PROCESS_SPEC: "stored-launch-spec", + ANNOTATION_SANDBOX_RUNTIME_LOG_LEVEL: "info", + ANNOTATION_SANDBOX_RUNTIME_READINESS: "unavailable", + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAPPING: "false", + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_OPERATION: "restart", + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_PHASE: "released", + SANDBOX_POD_NAME_ANNOTATION: "recorded-workload" + }, + "ownerReferences": [{"apiVersion":"v1", "kind":"Namespace", "name":"openshell", "uid":"namespace-uid"}] + }, + "spec": { + "volumeClaimTemplates": [{"metadata":{"name":"workspace"}, "spec":{"accessModes":["ReadWriteOnce"], "resources":{"requests":{"storage":"10Gi"}}}}], + "podTemplate": { + "metadata": {"annotations": {"operator.example/pod-injection": "preserved"}}, + "spec": { + "automountServiceAccountToken": false, + "serviceAccountName": "openshell-sandbox", + "imagePullSecrets": [{"name":"operator-registry"}], + "volumes": [ + {"name": SANDBOX_BOOTSTRAP_VOLUME_NAME, "secret":{"secretName":"old-bootstrap-secret"}}, + {"name":"data", "persistentVolumeClaim":{"claimName":"team-data", "readOnly":false}} + ], + "containers": [ + {"name":"agent", "image":"registry.example/agent:v1", "command":["/.openshell/runtime/openshell-sandbox"], "env":[{"name":"UNCHANGED","value":"yes"}], "volumeMounts":[{"name":"workspace", "mountPath":"/sandbox"}, {"name":"data", "mountPath":"/data", "subPath":"agent"}]}, + {"name":"operator-sidecar", "image":"registry.example/agent:v1"} + ], + "initContainers": [ + {"name":SANDBOX_BOOTSTRAP_CONTAINER_NAME, "image":"trusted-runtime:v3"}, + {"name":WORKSPACE_INIT_CONTAINER_NAME, "image":"registry.example/agent:v1", "command":["seed-workspace"], "volumeMounts":[{"name":"workspace", "mountPath":WORKSPACE_INIT_MOUNT_PATH}]}, + {"name":"operator-init", "image":"registry.example/agent:v1"} + ] + } + } + }, + "status":{"conditions":[{"type":"Suspended", "status":"True", "observedGeneration":7}]} + }); + if version == SANDBOX_VERSION_V1BETA1 { + value["spec"]["operatingMode"] = serde_json::json!("Suspended"); + } else { + value["spec"]["replicas"] = serde_json::json!(0); + } + value +} + +fn fixture( + version: &'static str, + operator: bool, +) -> (KubernetesComputeDriver, Arc>) { + let state = Arc::new(Mutex::new(ApiState { + sandbox: snapshot(version), + pvc: serde_json::json!({ + "apiVersion":"meta.k8s.io/v1", "kind":"PartialObjectMetadata", + "metadata": {"name":"team-data", "namespace":"openshell", "uid":"data-pvc-uid", + "labels": {"openshell.ai/sandbox-attachable":"true", "openshell.ai/sandbox-attachable-workspace":"team-a"}} + }), + pods: Vec::new(), + calls: Vec::new(), + patches: Vec::new(), + conflict: false, + pods_continue: false, + })); + let service_state = state.clone(); + let service = tower::service_fn(move |request: http::Request| { + let state = service_state.clone(); + async move { + let path = request.uri().path().to_string(); + let query = request.uri().query().unwrap_or_default().to_string(); + let method = request.method().clone(); + let body = request.into_body().collect().await.unwrap().to_bytes(); + let mut state = state.lock().unwrap(); + state.calls.push((method.clone(), path.clone())); + let value = match (method, path.as_str()) { + (http::Method::GET, path) if path.ends_with("/sandboxes") => { + serde_json::json!({"apiVersion":format!("{SANDBOX_GROUP}/{version}"), "kind":"SandboxList", "metadata":{}, "items":[state.sandbox]}) + } + (http::Method::GET, path) if path.ends_with("/sandboxes/sandbox-cr") => { + state.sandbox.clone() + } + ( + http::Method::GET, + "/api/v1/namespaces/openshell/persistentvolumeclaims/team-data", + ) => state.pvc.clone(), + (http::Method::GET, "/api/v1/namespaces/openshell/pods") => { + if state.pods_continue && !query.contains("continue=") { + serde_json::json!({"apiVersion":"v1", "kind":"PodList", "metadata":{"continue":"page-two"}, "items":[]}) + } else { + serde_json::json!({"apiVersion":"v1", "kind":"PodList", "metadata":{}, "items":state.pods}) + } + } + (http::Method::PATCH, path) if path.ends_with("/sandboxes/sandbox-cr") => { + let mutation: serde_json::Value = serde_json::from_slice(&body).unwrap(); + assert_eq!( + mutation["metadata"]["uid"], + state.sandbox["metadata"]["uid"] + ); + assert_eq!( + mutation["metadata"]["resourceVersion"], + state.sandbox["metadata"]["resourceVersion"] + ); + state.patches.push(mutation.clone()); + if state.conflict { + return Ok::<_, std::convert::Infallible>(response( + http::StatusCode::CONFLICT, + serde_json::json!({"apiVersion":"v1", "kind":"Status", "status":"Failure", "reason":"Conflict", "message":"concurrent start or edit", "code":409}), + )); + } + let next_version = state.sandbox["metadata"]["resourceVersion"] + .as_str() + .unwrap() + .parse::() + .unwrap() + + 1; + let next_generation = + state.sandbox["metadata"]["generation"].as_u64().unwrap() + 1; + merge(&mut state.sandbox, &mutation); + state.sandbox["metadata"]["resourceVersion"] = + serde_json::json!(next_version.to_string()); + state.sandbox["metadata"]["generation"] = serde_json::json!(next_generation); + state.sandbox.clone() + } + (method, path) => { + panic!("unexpected image update Kubernetes request {method} {path}") + } + }; + Ok::<_, std::convert::Infallible>(response(http::StatusCode::OK, value)) + } + }); + let client = Client::new(service, "openshell"); + let api_version = OnceCell::new(); + api_version.set(version).unwrap(); + let driver = KubernetesComputeDriver { + client: client.clone(), + watch_client: client, + sandbox_api_version: Arc::new(api_version), + lifecycle_gates: Arc::default(), + shared_supervisor: None, + config: KubernetesComputeConfig { + allow_driver_config: true, + workspace_mode: if operator { + WorkspaceMode::Operator + } else { + WorkspaceMode::Shared + }, + ..Default::default() + }, + operator_allowlist: operator.then(|| { + OperatorNamespaceAllowlist::from_set(std::iter::once("openshell".to_string()).collect()) + }), + }; + (driver, state) +} + +fn request() -> UpdateSandboxImageRequest { + UpdateSandboxImageRequest { + sandbox_id: "sandbox-1".into(), + name: "sandbox-name".into(), + image: "registry.example/agent:v2".into(), + expected_image: "registry.example/agent:v1".into(), + expected_runtime_identity: "kubernetes://openshell/resource-uid/old-supervisor-uid".into(), + operation_id: "stable-operation-1".into(), + validate_only: false, + revision: 1, + } +} + +#[tokio::test] +async fn image_update_preflight_validates_without_writes_and_apply_rechecks() { + let (driver, state) = fixture(SANDBOX_VERSION_V1BETA1, false); + let before = state.lock().unwrap().sandbox.clone(); + let mut preflight = request(); + preflight.validate_only = true; + driver.update_sandbox_image(&preflight).await.unwrap(); + { + let state = state.lock().unwrap(); + assert!(state.patches.is_empty()); + assert_eq!(state.sandbox, before); + assert!( + state + .calls + .iter() + .any(|(_, path)| path.ends_with("/persistentvolumeclaims/team-data")) + ); + assert!(state.calls.iter().any(|(_, path)| path.ends_with("/pods"))); + } + // Operator approval can change between preflight and apply. Both requests + // independently enforce the entire stopped/admission check. + state.lock().unwrap().pvc["metadata"]["labels"] = serde_json::json!({}); + assert!(matches!( + driver.update_sandbox_image(&request()).await, + Err(KubernetesDriverError::Precondition(_)) + )); + assert!(matches!( + driver.update_sandbox_image(&preflight).await, + Err(KubernetesDriverError::Precondition(_)) + )); + assert!(state.lock().unwrap().patches.is_empty()); +} + +#[tokio::test] +async fn image_update_capability_is_optional() { + let (driver, _) = fixture(SANDBOX_VERSION_V1BETA1, false); + let metadata = driver.capabilities().unwrap().extension.unwrap(); + let capability = openshell_core::extension_protocol::COMPUTE_UPDATE_SANDBOX_IMAGE; + assert!( + metadata + .supported_capabilities + .iter() + .any(|value| value == capability) + ); + assert!( + !metadata + .required_capabilities + .iter() + .any(|value| value == capability) + ); +} + +#[tokio::test] +async fn image_update_preserves_resource_storage_and_operator_bootstrap_annotations() { + for version in [SANDBOX_VERSION_V1BETA1, SANDBOX_VERSION_V1ALPHA1] { + for operator in [false, true] { + let (driver, state) = fixture(version, operator); + if operator { + // Operator-mode identity can live in bootstrap annotations + // rather than a caller-visible workload-name label. + state.lock().unwrap().sandbox["metadata"]["labels"] + .as_object_mut() + .unwrap() + .remove(LABEL_SANDBOX_NAME); + } + let before = state.lock().unwrap().sandbox.clone(); + let pvc_before = state.lock().unwrap().pvc.clone(); + driver.update_sandbox_image(&request()).await.unwrap(); + let state = state.lock().unwrap(); + let mut expected = before; + expected["metadata"]["resourceVersion"] = serde_json::json!("43"); + expected["metadata"]["generation"] = serde_json::json!(8); + expected["metadata"]["annotations"][IMAGE_UPDATE_ANNOTATION] = serde_json::json!( + serde_json::to_string(&ImageUpdateRecord { + configuration: false, + command_updated: false, + environment_updated: false, + operation_id: request().operation_id, + expected_image: request().expected_image, + image: request().image, + revision: request().revision, + }) + .unwrap() + ); + expected["spec"]["podTemplate"]["spec"]["containers"][0]["image"] = + serde_json::json!(request().image); + expected["spec"]["podTemplate"]["spec"]["initContainers"][1]["image"] = + serde_json::json!(request().image); + // Whole-object equality proves claim templates, mounts, owner UID, + // launch state, operator annotations, and trusted/sidecar images + // were preserved. The API rejects any unexpected DELETE/POST. + assert_eq!(state.sandbox, expected); + assert_eq!(state.pvc, pvc_before); + assert_eq!(state.patches.len(), 1); + assert_eq!( + state.patches[0]["metadata"]["annotations"] + .as_object() + .unwrap() + .len(), + 1 + ); + assert!( + state.patches[0]["spec"] + .get("volumeClaimTemplates") + .is_none() + ); + assert!( + state.patches[0]["spec"]["podTemplate"]["spec"] + .get("volumes") + .is_none() + ); + } + } +} + +#[tokio::test] +async fn image_update_retry_is_durable_and_does_not_patch_twice() { + let (driver, state) = fixture(SANDBOX_VERSION_V1BETA1, false); + driver.update_sandbox_image(&request()).await.unwrap(); + // Simulate a process restart by dropping the original in-memory gate state. + let mut restarted = driver.clone(); + restarted.lifecycle_gates = Arc::default(); + restarted.update_sandbox_image(&request()).await.unwrap(); + assert_eq!(state.lock().unwrap().patches.len(), 1); + let mut other_operation = request(); + other_operation.operation_id = "other-operation".into(); + other_operation.revision += 1; + assert!(matches!( + driver.update_sandbox_image(&other_operation).await, + Err(KubernetesDriverError::Precondition(_)) + )); + let mut reused_operation = request(); + reused_operation.image = "registry.example/agent:v3".into(); + assert!(matches!( + driver.update_sandbox_image(&reused_operation).await, + Err(KubernetesDriverError::Precondition(_)) + )); + assert_eq!(state.lock().unwrap().patches.len(), 1); +} + +#[tokio::test] +async fn image_update_monotonic_revision_rejects_late_replay_after_a_tag_round_trip() { + let (driver, state) = fixture(SANDBOX_VERSION_V1BETA1, false); + let a = request(); + driver.update_sandbox_image(&a).await.unwrap(); + let mut b = a.clone(); + b.expected_image = a.image.clone(); + b.image = a.expected_image.clone(); + b.operation_id = "operation-b".into(); + b.revision = 2; + driver.update_sandbox_image(&b).await.unwrap(); + let before = state.lock().unwrap().sandbox.clone(); + // The current tag again matches A's expected image, but its revision is + // stale. The latest evidence must reject A rather than reapply it. + assert!(matches!(driver.update_sandbox_image(&a).await, + Err(KubernetesDriverError::Precondition(message)) if message.contains("older"))); + let mut conflicting = a.clone(); + conflicting.operation_id = "different-operation-at-revision-2".into(); + conflicting.revision = b.revision; + assert!(matches!(driver.update_sandbox_image(&conflicting).await, + Err(KubernetesDriverError::Precondition(message)) if message.contains("revision was already used"))); + assert_eq!(state.lock().unwrap().patches.len(), 2); + assert_eq!(state.lock().unwrap().sandbox, before); + driver.update_sandbox_image(&b).await.unwrap(); + assert_eq!(state.lock().unwrap().patches.len(), 2); + conflicting.revision = 3; + driver.update_sandbox_image(&conflicting).await.unwrap(); + assert_eq!(state.lock().unwrap().patches.len(), 3); +} + +#[tokio::test] +async fn image_update_rejects_zero_revision_before_kubernetes_io() { + let (driver, state) = fixture(SANDBOX_VERSION_V1BETA1, false); + let mut request = request(); + request.revision = 0; + assert!(matches!( + driver.update_sandbox_image(&request).await, + Err(KubernetesDriverError::InvalidArgument(_)) + )); + assert!(state.lock().unwrap().calls.is_empty()); +} + +#[tokio::test] +async fn image_update_rejects_running_bootstrapping_and_unspecified_operating_state() { + for version in [SANDBOX_VERSION_V1BETA1, SANDBOX_VERSION_V1ALPHA1] { + for scenario in ["running", "bootstrap", "missing", "deleting"] { + let (driver, state) = fixture(version, false); + { + let mut state = state.lock().unwrap(); + match scenario { + "running" if version == SANDBOX_VERSION_V1BETA1 => { + state.sandbox["spec"]["operatingMode"] = serde_json::json!("Running"); + } + "running" => state.sandbox["spec"]["replicas"] = serde_json::json!(1), + "bootstrap" => { + state.sandbox["metadata"]["annotations"] + [ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAPPING] = serde_json::json!("true"); + } + "missing" => { + state.sandbox["spec"].as_object_mut().unwrap().remove( + if version == SANDBOX_VERSION_V1BETA1 { + "operatingMode" + } else { + "replicas" + }, + ); + } + "deleting" => { + state.sandbox["metadata"]["deletionTimestamp"] = + serde_json::json!("2026-10-15T18:00:00Z"); + } + _ => unreachable!(), + } + } + assert!( + matches!( + driver.update_sandbox_image(&request()).await, + Err(KubernetesDriverError::Precondition(_)) + ), + "{scenario} {version}" + ); + assert!(state.lock().unwrap().patches.is_empty()); + } + } +} + +#[tokio::test] +async fn image_update_checks_actual_workload_and_supervisor_pods_including_later_pages() { + for metadata in [ + serde_json::json!({"name":"controller-workload", "ownerReferences":[{"apiVersion":"agents.x-k8s.io/v1beta1", "kind":"Sandbox", "name":"sandbox-cr", "uid":"resource-uid"}]}), + serde_json::json!({"name":"supervisor", "labels":{LABEL_SANDBOX_ID:"sandbox-1"}}), + serde_json::json!({"name":"recorded-workload", "deletionTimestamp":"2026-10-15T18:00:00Z"}), + serde_json::json!({"name":"workload-with-annotation", "annotations":{LABEL_SANDBOX_ID:"sandbox-1"}}), + ] { + let (driver, state) = fixture(SANDBOX_VERSION_V1BETA1, false); + { + let mut state = state.lock().unwrap(); + state + .pods + .push(serde_json::json!({"apiVersion":"v1", "kind":"Pod", "metadata":metadata})); + state.pods_continue = true; + } + let result = driver.update_sandbox_image(&request()).await; + assert!( + matches!(result, Err(KubernetesDriverError::Precondition(message)) if message.contains("Pods still exist")) + ); + assert!(state.lock().unwrap().patches.is_empty()); + } +} + +#[tokio::test] +async fn image_update_rejects_image_name_and_runtime_identity_conflicts() { + for scenario in ["image", "name", "runtime", "seed", "missing-provenance"] { + let (driver, state) = fixture(SANDBOX_VERSION_V1BETA1, false); + let mut request = request(); + match scenario { + "image" => request.expected_image = "registry.example/agent:other".into(), + "name" => request.name = "different-name".into(), + "runtime" => { + request.expected_runtime_identity = + "kubernetes://openshell/replacement-uid/old-supervisor".into(); + } + "seed" => { + state.lock().unwrap().sandbox["spec"]["podTemplate"]["spec"]["initContainers"][1] + ["image"] = serde_json::json!("different-seed:v1"); + } + "missing-provenance" => { + state.lock().unwrap().sandbox["metadata"]["annotations"] + .as_object_mut() + .unwrap() + .remove(crate::resource_admission::CONFIG_USED); + } + _ => unreachable!(), + } + assert!( + matches!( + driver.update_sandbox_image(&request).await, + Err(KubernetesDriverError::Precondition(_)) + ), + "{scenario}" + ); + assert!(state.lock().unwrap().patches.is_empty()); + } +} + +#[tokio::test] +async fn image_update_revalidates_attachment_admission_and_uid_even_on_retry() { + for retry in [false, true] { + for scenario in ["labels", "uid", "config-disabled"] { + let (mut driver, state) = fixture(SANDBOX_VERSION_V1BETA1, false); + if retry { + driver.update_sandbox_image(&request()).await.unwrap(); + } + match scenario { + "labels" => state.lock().unwrap().pvc["metadata"]["labels"] = serde_json::json!({}), + "uid" => { + state.lock().unwrap().pvc["metadata"]["uid"] = + serde_json::json!("replacement-pvc-uid"); + } + "config-disabled" => driver.config.allow_driver_config = false, + _ => unreachable!(), + } + assert!( + matches!( + driver.update_sandbox_image(&request()).await, + Err(KubernetesDriverError::Precondition(_)) + ), + "{scenario} retry={retry}" + ); + assert_eq!(state.lock().unwrap().patches.len(), usize::from(retry)); + } + } +} + +#[tokio::test] +async fn image_update_conflict_does_not_blindly_retry_or_recreate() { + let (driver, state) = fixture(SANDBOX_VERSION_V1BETA1, false); + state.lock().unwrap().conflict = true; + let before = state.lock().unwrap().sandbox.clone(); + assert!( + matches!(driver.update_sandbox_image(&request()).await, Err(KubernetesDriverError::Precondition(message)) if message.contains("changed during image update")) + ); + let state = state.lock().unwrap(); + assert_eq!(state.patches.len(), 1); + assert_eq!(state.sandbox, before); +} + +#[tokio::test] +async fn image_update_grpc_maps_validation_and_precondition_errors() { + use openshell_core::proto::compute::v1::compute_driver_image_updates_server::ComputeDriverImageUpdates; + let (driver, state) = fixture(SANDBOX_VERSION_V1BETA1, false); + let service = crate::ComputeDriverService::new(driver); + let mut invalid = request(); + invalid.image = "https://registry.example/image:v2".into(); + let error = service + .update_sandbox_image(tonic::Request::new(invalid)) + .await + .unwrap_err(); + assert_eq!(error.code(), tonic::Code::InvalidArgument); + assert!(state.lock().unwrap().calls.is_empty()); + let mut conflict = request(); + conflict.expected_image = "other:v1".into(); + let error = service + .update_sandbox_image(tonic::Request::new(conflict)) + .await + .unwrap_err(); + assert_eq!(error.code(), tonic::Code::FailedPrecondition); +} + +#[tokio::test] +async fn image_update_can_repair_a_legacy_invalid_image_reference() { + let (driver, state) = fixture(SANDBOX_VERSION_V1BETA1, false); + let invalid_image = "UpperCaseRepository"; + { + let mut state = state.lock().unwrap(); + state.sandbox["spec"]["podTemplate"]["spec"]["containers"][0]["image"] = + serde_json::json!(invalid_image); + state.sandbox["spec"]["podTemplate"]["spec"]["initContainers"][1]["image"] = + serde_json::json!(invalid_image); + } + let mut update = request(); + update.expected_image = invalid_image.into(); + driver.update_sandbox_image(&update).await.unwrap(); + let state = state.lock().unwrap(); + assert_eq!(state.patches.len(), 1); + assert_eq!( + state.sandbox["spec"]["podTemplate"]["spec"]["containers"][0]["image"], + update.image + ); + assert_eq!(state.sandbox["metadata"]["uid"], "resource-uid"); +} + +#[test] +fn image_update_validates_container_references() { + for image in [ + "agent", + "registry.example:5000/team/agent:v2", + "localhost/agent:Tag_1", + "[::1]:5000/team/agent", + "team/image__name:v2", + &format!("registry.example/agent@sha256:{}", "a".repeat(64)), + ] { + validate_workload_image(image).unwrap(); + } + for image in [ + "", + " image:v2", + "image:v2\n", + "https://host/image", + "host//image", + "UpperCaseRepository", + "agent:", + "agent@sha256:invalid", + "agent@sha256:abc", + &format!("agent@sha256:{}", "a".repeat(32)), + &format!("agent@sha256:{}", "a".repeat(65)), + &format!("{}:v1", "a".repeat(256)), + "../agent", + &"a".repeat(2049), + ] { + assert!(validate_workload_image(image).is_err(), "{image:?}"); + } +} + +#[tokio::test] +async fn image_update_serializes_with_other_lifecycle_operations() { + let (driver, state) = fixture(SANDBOX_VERSION_V1BETA1, false); + let guard = driver + .lifecycle_gates + .gate_for("sandbox-1") + .lock_owned() + .await; + let request = request(); + let pending = driver.update_sandbox_image(&request); + tokio::pin!(pending); + assert!( + tokio::time::timeout(Duration::from_millis(10), &mut pending) + .await + .is_err() + ); + assert!(state.lock().unwrap().calls.is_empty()); + drop(guard); + pending.await.unwrap(); +} + +#[tokio::test] +async fn configuration_update_atomically_changes_restart_inputs_and_retries_without_writes() { + use openshell_core::proto::compute::v1::{ + SandboxEnvironmentUpdate, SandboxMainProcessUpdate, UpdateSandboxConfigurationRequest, + }; + for version in [SANDBOX_VERSION_V1ALPHA1, SANDBOX_VERSION_V1BETA1] { + let (driver, state) = fixture(version, false); + { + let mut state = state.lock().unwrap(); + state.sandbox["metadata"]["annotations"] + [ANNOTATION_SANDBOX_RUNTIME_MAIN_PROCESS_SPEC] = serde_json::json!( + openshell_core::sandbox_env::MainProcessConfig::encode_driver_spec(None).unwrap() + ); + state.sandbox["spec"]["podTemplate"]["spec"]["containers"][0]["env"] + .as_array_mut() + .unwrap() + .push(serde_json::json!({"name":"OPENSHELL_TEST_RUNTIME", "value":"protected"})); + } + let before = state.lock().unwrap().sandbox.clone(); + let mut update = UpdateSandboxConfigurationRequest { + image_update: Some(request()), + main_process: Some(SandboxMainProcessUpdate { + command: vec!["/usr/local/bin/start-agent".into(), "--new".into()], + ..Default::default() + }), + environment: Some(SandboxEnvironmentUpdate { + values: std::collections::HashMap::from([( + "AGENT_MODE".into(), + "sensitive-environment-value".into(), + )]), + }), + }; + update.image_update.as_mut().unwrap().validate_only = true; + driver.update_sandbox_configuration(&update).await.unwrap(); + assert_eq!(state.lock().unwrap().sandbox, before); + update.image_update.as_mut().unwrap().validate_only = false; + driver.update_sandbox_configuration(&update).await.unwrap(); + { + let state = state.lock().unwrap(); + assert_eq!(state.patches.len(), 1); + assert_eq!( + state.sandbox["spec"]["volumeClaimTemplates"], + before["spec"]["volumeClaimTemplates"] + ); + let agent = &state.sandbox["spec"]["podTemplate"]["spec"]["containers"][0]; + assert_eq!( + agent["command"], + before["spec"]["podTemplate"]["spec"]["containers"][0]["command"] + ); + assert_eq!(agent["image"], update.image_update.as_ref().unwrap().image); + let env = agent["env"].as_array().unwrap(); + assert!(env.iter().any(|entry| entry["name"] == "AGENT_MODE" + && entry["value"] == "sensitive-environment-value")); + assert!( + env.iter() + .any(|entry| entry["name"] == "OPENSHELL_TEST_RUNTIME" + && entry["value"] == "protected") + ); + assert!(!env.iter().any(|entry| entry["name"] == "UNCHANGED")); + let encoded = state.sandbox["metadata"]["annotations"] + [ANNOTATION_SANDBOX_RUNTIME_MAIN_PROCESS_SPEC] + .as_str() + .unwrap(); + assert_eq!( + openshell_core::sandbox_env::MainProcessConfig::decode(encoded) + .unwrap() + .command, + update.main_process.as_ref().unwrap().command + ); + let evidence = state.sandbox["metadata"]["annotations"][IMAGE_UPDATE_ANNOTATION] + .as_str() + .unwrap(); + assert!( + !evidence.contains("sensitive-environment-value"), + "operation evidence must not contain environment values" + ); + } + driver.update_sandbox_configuration(&update).await.unwrap(); + assert_eq!(state.lock().unwrap().patches.len(), 1); + let mut changed = update.clone(); + changed + .environment + .as_mut() + .unwrap() + .values + .insert("AGENT_MODE".into(), "different".into()); + assert!(driver.update_sandbox_configuration(&changed).await.is_err()); + changed = update.clone(); + changed + .main_process + .as_mut() + .unwrap() + .command + .push("--different".into()); + assert!(driver.update_sandbox_configuration(&changed).await.is_err()); + update.image_update.as_mut().unwrap().expected_image = + update.image_update.as_ref().unwrap().image.clone(); + update.image_update.as_mut().unwrap().revision += 1; + update.image_update.as_mut().unwrap().operation_id = "clear-configuration".into(); + update.main_process.as_mut().unwrap().command.clear(); + update.environment.as_mut().unwrap().values.clear(); + driver.update_sandbox_configuration(&update).await.unwrap(); + let state = state.lock().unwrap(); + let encoded = state.sandbox["metadata"]["annotations"] + [ANNOTATION_SANDBOX_RUNTIME_MAIN_PROCESS_SPEC] + .as_str() + .unwrap(); + let main = openshell_core::sandbox_env::MainProcessConfig::decode(encoded).unwrap(); + assert!(main.command.is_empty() && main.tty); + assert_eq!( + state.sandbox["spec"]["podTemplate"]["spec"]["containers"][0]["env"] + .as_array() + .unwrap() + .len(), + 1 + ); + } +} + +#[tokio::test] +async fn configuration_update_rejects_reserved_environment_without_writes() { + use openshell_core::proto::compute::v1::{ + SandboxEnvironmentUpdate, UpdateSandboxConfigurationRequest, + }; + let (driver, state) = fixture(SANDBOX_VERSION_V1BETA1, false); + let update = UpdateSandboxConfigurationRequest { + image_update: Some(request()), + main_process: None, + environment: Some(SandboxEnvironmentUpdate { + values: std::collections::HashMap::from([( + "OPENSHELL_SESSION".into(), + "forged".into(), + )]), + }), + }; + assert!(matches!( + driver.update_sandbox_configuration(&update).await, + Err(KubernetesDriverError::InvalidArgument(_)) + )); + assert!(state.lock().unwrap().calls.is_empty()); +} diff --git a/crates/openshell-driver-kubernetes/src/main.rs b/crates/openshell-driver-kubernetes/src/main.rs index 209d286370..deb360c457 100644 --- a/crates/openshell-driver-kubernetes/src/main.rs +++ b/crates/openshell-driver-kubernetes/src/main.rs @@ -9,6 +9,7 @@ use std::path::PathBuf; use tracing::info; use openshell_core::VERSION; +use openshell_core::proto::compute::v1::compute_driver_image_updates_server::ComputeDriverImageUpdatesServer; use openshell_core::proto::compute::v1::compute_driver_server::ComputeDriverServer; use openshell_driver_kubernetes::{ ComputeDriverService, DEFAULT_GATEWAY_ID, DEFAULT_SANDBOX_SERVICE_ACCOUNT_NAME, @@ -333,7 +334,10 @@ async fn main() -> Result<()> { .await .into_diagnostic()?; - let service = ComputeDriverServer::new(ComputeDriverService::new(driver)); + let driver_service = ComputeDriverService::new(driver); + let configuration_updates_service = openshell_core::proto::compute::v1::compute_driver_configuration_updates_server::ComputeDriverConfigurationUpdatesServer::new(driver_service.clone()); + let image_updates_service = ComputeDriverImageUpdatesServer::new(driver_service.clone()); + let service = ComputeDriverServer::new(driver_service); let shutdown = async move { shutdown_signal().await; let _ = shutdown_tx.send(true); @@ -347,6 +351,8 @@ async fn main() -> Result<()> { tonic::transport::Server::builder() .layer(openshell_otel::compute_driver_rpc_layer()) .add_service(service) + .add_service(image_updates_service) + .add_service(configuration_updates_service) .serve_with_incoming_shutdown( openshell_core::external_driver_socket::SameUidUnixIncoming::new(listener), shutdown, @@ -358,6 +364,8 @@ async fn main() -> Result<()> { tonic::transport::Server::builder() .layer(openshell_otel::compute_driver_rpc_layer()) .add_service(service) + .add_service(image_updates_service) + .add_service(configuration_updates_service) .serve_with_shutdown(args.bind_address, shutdown) .await .into_diagnostic() diff --git a/crates/openshell-driver-mxc/src/grpc.rs b/crates/openshell-driver-mxc/src/grpc.rs index 4c21172c47..e2b4f57461 100644 --- a/crates/openshell-driver-mxc/src/grpc.rs +++ b/crates/openshell-driver-mxc/src/grpc.rs @@ -32,6 +32,9 @@ impl ComputeDriverService { } } +#[tonic::async_trait] +impl openshell_core::compute::ComputeDriver for ComputeDriverService {} + #[tonic::async_trait] impl ComputeDriver for ComputeDriverService { async fn get_capabilities( diff --git a/crates/openshell-driver-podman/src/grpc.rs b/crates/openshell-driver-podman/src/grpc.rs index 25cfd09d7a..58925a02b9 100644 --- a/crates/openshell-driver-podman/src/grpc.rs +++ b/crates/openshell-driver-podman/src/grpc.rs @@ -48,6 +48,9 @@ impl ComputeDriverService { } } +#[tonic::async_trait] +impl openshell_core::compute::ComputeDriver for ComputeDriverService {} + #[tonic::async_trait] impl ComputeDriver for ComputeDriverService { async fn authenticate_sandbox( diff --git a/crates/openshell-driver-vm/src/driver.rs b/crates/openshell-driver-vm/src/driver.rs index 0a1176e7c9..44f85cdb26 100644 --- a/crates/openshell-driver-vm/src/driver.rs +++ b/crates/openshell-driver-vm/src/driver.rs @@ -4865,6 +4865,9 @@ fn configure_main_exit_marker(command: &mut Command, state_dir: &Path) { .arg(state_dir.join(MAIN_PROCESS_EXITED_FILE)); } +#[tonic::async_trait] +impl openshell_core::compute::ComputeDriver for VmDriver {} + #[tonic::async_trait] impl ComputeDriver for VmDriver { async fn authenticate_sandbox( diff --git a/crates/openshell-gateway-interceptors/src/routes.rs b/crates/openshell-gateway-interceptors/src/routes.rs index c47f42e40d..30e053e323 100644 --- a/crates/openshell-gateway-interceptors/src/routes.rs +++ b/crates/openshell-gateway-interceptors/src/routes.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -//! Interceptable `OpenShell` route classification. +//! Interceptable public gateway route classification. use std::collections::{BTreeMap, BTreeSet}; @@ -10,6 +10,10 @@ use prost_reflect::DescriptorPool; use crate::{InterceptorError, Result}; const SERVICE_OPEN_SHELL: &str = "openshell.v1.OpenShell"; +const SERVICE_SANDBOX_UPDATES: &str = "openshell.v1.SandboxUpdates"; + +/// Unary sandbox-update methods that may be targeted by gateway interceptors. +pub const INTERCEPTABLE_SANDBOX_UPDATE_METHODS: &[&str] = &["UpdateSandbox"]; /// Unary `openshell.v1.OpenShell` methods that may be targeted by gateway /// interceptors. New methods are non-interceptable until deliberately added @@ -58,25 +62,31 @@ impl OpenShellRouteIndex { } pub(crate) fn from_descriptor_pool(pool: &DescriptorPool) -> Result { - let service = pool + let open_shell = pool .get_service_by_name(SERVICE_OPEN_SHELL) .ok_or_else(|| { InterceptorError::Config(format!( "descriptor set does not contain service '{SERVICE_OPEN_SHELL}'" )) })?; + // SandboxUpdates is an optional extension: baseline descriptor sets + // still need only OpenShell, but a present extension must be valid. + let sandbox_updates = pool.get_service_by_name(SERVICE_SANDBOX_UPDATES); let mut all_methods = BTreeSet::new(); let mut unary_methods = BTreeSet::new(); let mut input_types = BTreeMap::new(); let mut output_types = BTreeMap::new(); - for method in service.methods() { - let name = method.name().to_string(); - all_methods.insert(name.clone()); - if !method.is_client_streaming() && !method.is_server_streaming() { - unary_methods.insert(name.clone()); - input_types.insert(name.clone(), method.input().full_name().to_string()); - output_types.insert(name, method.output().full_name().to_string()); + for service in std::iter::once(&open_shell).chain(sandbox_updates.as_ref()) { + let service_name = service.full_name(); + for method in service.methods() { + let route = format!("{service_name}/{}", method.name()); + all_methods.insert(route.clone()); + if !method.is_client_streaming() && !method.is_server_streaming() { + unary_methods.insert(route.clone()); + input_types.insert(route.clone(), method.input().full_name().to_string()); + output_types.insert(route, method.output().full_name().to_string()); + } } } @@ -86,43 +96,49 @@ impl OpenShellRouteIndex { input_types, output_types, }; - index.validate_interceptable_list()?; + index.validate_interceptable_list(SERVICE_OPEN_SHELL, INTERCEPTABLE_METHODS)?; + if sandbox_updates.is_some() { + index.validate_interceptable_list( + SERVICE_SANDBOX_UPDATES, + INTERCEPTABLE_SANDBOX_UPDATE_METHODS, + )?; + } Ok(index) } #[must_use] pub fn is_interceptable(&self, service: &str, method: &str) -> bool { - service == SERVICE_OPEN_SHELL - && self.unary_methods.contains(method) - && INTERCEPTABLE_METHODS.contains(&method) + let allowed = match service { + SERVICE_OPEN_SHELL => INTERCEPTABLE_METHODS, + SERVICE_SANDBOX_UPDATES => INTERCEPTABLE_SANDBOX_UPDATE_METHODS, + _ => return false, + }; + allowed.contains(&method) && self.unary_methods.contains(&format!("{service}/{method}")) } #[must_use] pub fn input_type(&self, service: &str, method: &str) -> Option<&str> { - if service == SERVICE_OPEN_SHELL && self.unary_methods.contains(method) { - self.input_types.get(method).map(String::as_str) - } else { - None - } + self.input_types + .get(&format!("{service}/{method}")) + .map(String::as_str) } #[must_use] pub fn output_type(&self, service: &str, method: &str) -> Option<&str> { - if service == SERVICE_OPEN_SHELL && self.unary_methods.contains(method) { - self.output_types.get(method).map(String::as_str) - } else { - None - } + self.output_types + .get(&format!("{service}/{method}")) + .map(String::as_str) } - fn validate_interceptable_list(&self) -> Result<()> { + fn validate_interceptable_list(&self, service: &str, methods: &[&str]) -> Result<()> { let mut stale = Vec::new(); let mut streaming = Vec::new(); - for method in INTERCEPTABLE_METHODS { - if !self.all_methods.contains(*method) { - stale.push((*method).to_string()); - } else if !self.unary_methods.contains(*method) { - streaming.push((*method).to_string()); + for method in methods { + let route = format!("{service}/{method}"); + if !self.all_methods.contains(&route) { + stale.push(route); + } else if !self.unary_methods.contains(&route) { + streaming.push(route); } } if !stale.is_empty() { @@ -141,8 +157,158 @@ impl OpenShellRouteIndex { #[cfg(test)] mod tests { + use prost::Message; + use prost_types::{FileDescriptorSet, ServiceDescriptorProto}; + use super::*; + fn descriptor_set() -> FileDescriptorSet { + FileDescriptorSet::decode(openshell_core::FILE_DESCRIPTOR_SET).unwrap() + } + + fn service_mut<'a>( + descriptors: &'a mut FileDescriptorSet, + name: &str, + ) -> &'a mut ServiceDescriptorProto { + descriptors + .file + .iter_mut() + .filter(|file| file.package.as_deref() == Some("openshell.v1")) + .flat_map(|file| &mut file.service) + .find(|service| service.name.as_deref() == Some(name)) + .unwrap() + } + + #[test] + fn open_shell_only_descriptor_set_is_accepted() { + let mut descriptors = descriptor_set(); + for file in &mut descriptors.file { + file.service.retain(|service| { + file.package.as_deref() == Some("openshell.v1") + && service.name.as_deref() == Some("OpenShell") + }); + } + + let index = OpenShellRouteIndex::from_descriptor_set(&descriptors.encode_to_vec()).unwrap(); + for method in INTERCEPTABLE_METHODS { + assert!(index.is_interceptable(SERVICE_OPEN_SHELL, method)); + assert!(index.input_type(SERVICE_OPEN_SHELL, method).is_some()); + assert!(index.output_type(SERVICE_OPEN_SHELL, method).is_some()); + } + assert!(!index.is_interceptable(SERVICE_SANDBOX_UPDATES, "UpdateSandbox")); + assert!( + index + .input_type(SERVICE_SANDBOX_UPDATES, "UpdateSandbox") + .is_none() + ); + assert!( + index + .output_type(SERVICE_SANDBOX_UPDATES, "UpdateSandbox") + .is_none() + ); + } + + #[test] + fn missing_open_shell_service_is_rejected() { + let mut descriptors = descriptor_set(); + for file in &mut descriptors.file { + file.service + .retain(|service| service.name.as_deref() != Some("OpenShell")); + } + + let error = + OpenShellRouteIndex::from_descriptor_set(&descriptors.encode_to_vec()).unwrap_err(); + assert!(error.to_string().contains(&format!( + "descriptor set does not contain service '{SERVICE_OPEN_SHELL}'" + ))); + } + + #[test] + fn present_sandbox_updates_service_requires_update_sandbox() { + let mut descriptors = descriptor_set(); + service_mut(&mut descriptors, "SandboxUpdates") + .method + .clear(); + + let error = + OpenShellRouteIndex::from_descriptor_set(&descriptors.encode_to_vec()).unwrap_err(); + assert!(error.to_string().contains("stale methods")); + assert!( + error + .to_string() + .contains("openshell.v1.SandboxUpdates/UpdateSandbox") + ); + } + + #[test] + fn streaming_update_sandbox_is_rejected() { + for (client_streaming, server_streaming) in [(true, false), (false, true), (true, true)] { + let mut descriptors = descriptor_set(); + let method = &mut service_mut(&mut descriptors, "SandboxUpdates").method[0]; + assert_eq!(method.name.as_deref(), Some("UpdateSandbox")); + method.client_streaming = Some(client_streaming); + method.server_streaming = Some(server_streaming); + + let error = + OpenShellRouteIndex::from_descriptor_set(&descriptors.encode_to_vec()).unwrap_err(); + assert!(error.to_string().contains("streaming methods")); + assert!( + error + .to_string() + .contains("openshell.v1.SandboxUpdates/UpdateSandbox") + ); + } + } + + #[test] + fn unlisted_sandbox_update_methods_are_not_interceptable() { + let mut descriptors = descriptor_set(); + let service = service_mut(&mut descriptors, "SandboxUpdates"); + let mut future_method = service.method[0].clone(); + future_method.name = Some("FutureUnaryMethod".to_string()); + service.method.push(future_method); + + let index = OpenShellRouteIndex::from_descriptor_set(&descriptors.encode_to_vec()).unwrap(); + assert!(index.is_interceptable(SERVICE_SANDBOX_UPDATES, "UpdateSandbox")); + assert!(!index.is_interceptable(SERVICE_SANDBOX_UPDATES, "FutureUnaryMethod")); + assert_eq!( + index.input_type(SERVICE_SANDBOX_UPDATES, "FutureUnaryMethod"), + Some("openshell.v1.UpdateSandboxRequest") + ); + } + + #[test] + fn open_shell_allowlist_validation_remains_required() { + let mut missing_method = descriptor_set(); + service_mut(&mut missing_method, "OpenShell") + .method + .retain(|method| method.name.as_deref() != Some("CreateSandbox")); + let error = + OpenShellRouteIndex::from_descriptor_set(&missing_method.encode_to_vec()).unwrap_err(); + assert!(error.to_string().contains("stale methods")); + assert!( + error + .to_string() + .contains("openshell.v1.OpenShell/CreateSandbox") + ); + + let mut streaming_method = descriptor_set(); + let method = service_mut(&mut streaming_method, "OpenShell") + .method + .iter_mut() + .find(|method| method.name.as_deref() == Some("CreateSandbox")) + .unwrap(); + method.server_streaming = Some(true); + let error = OpenShellRouteIndex::from_descriptor_set(&streaming_method.encode_to_vec()) + .unwrap_err(); + assert!(error.to_string().contains("streaming methods")); + assert!( + error + .to_string() + .contains("openshell.v1.OpenShell/CreateSandbox") + ); + } + #[test] fn interceptable_entries_match_real_unary_methods() { OpenShellRouteIndex::from_descriptor_set(openshell_core::FILE_DESCRIPTOR_SET).unwrap(); @@ -153,6 +319,26 @@ mod tests { let index = OpenShellRouteIndex::from_descriptor_set(openshell_core::FILE_DESCRIPTOR_SET).unwrap(); assert!(index.is_interceptable("openshell.v1.OpenShell", "CreateSandbox")); + assert!(index.is_interceptable(SERVICE_SANDBOX_UPDATES, "UpdateSandbox")); + assert!(!index.is_interceptable(SERVICE_OPEN_SHELL, "UpdateSandbox")); + assert!(!index.is_interceptable(SERVICE_SANDBOX_UPDATES, "CreateSandbox")); + assert_eq!( + index.input_type(SERVICE_OPEN_SHELL, "CreateSandbox"), + Some("openshell.v1.CreateSandboxRequest") + ); + assert_eq!( + index.input_type(SERVICE_SANDBOX_UPDATES, "UpdateSandbox"), + Some("openshell.v1.UpdateSandboxRequest") + ); + assert_eq!( + index.output_type(SERVICE_SANDBOX_UPDATES, "UpdateSandbox"), + Some("openshell.v1.SandboxResponse") + ); + assert!( + index + .input_type(SERVICE_OPEN_SHELL, "UpdateSandbox") + .is_none() + ); assert!(index.is_interceptable("openshell.v1.OpenShell", "UpdateConfig")); assert!(index.is_interceptable("openshell.v1.OpenShell", "SubmitPolicyAnalysis")); assert!(!index.is_interceptable("openshell.v1.OpenShell", "Health")); diff --git a/crates/openshell-gateway/src/lib.rs b/crates/openshell-gateway/src/lib.rs index 3f060ae9f7..cd2a3c1e97 100644 --- a/crates/openshell-gateway/src/lib.rs +++ b/crates/openshell-gateway/src/lib.rs @@ -278,6 +278,18 @@ impl openshell_server::ComputeDriverFactory for KubernetesFactory { &self, context: openshell_server::ComputeDriverBuildContext<'_>, ) -> openshell_core::Result { + self.build_with_image_updates(context) + .await + .map(|(instance, _)| instance) + } + + async fn build_with_image_updates( + &self, + context: openshell_server::ComputeDriverBuildContext<'_>, + ) -> openshell_core::Result<( + openshell_server::ComputeDriverInstance, + Option, + )> { let mut config = kubernetes_config(context.config_context())?; config.supervisor_otlp_endpoint = context .otlp_config() @@ -303,9 +315,12 @@ impl openshell_server::ComputeDriverFactory for KubernetesFactory { .await } .map_err(|error| openshell_core::Error::execution(error.to_string()))?; - let driver = openshell_driver_kubernetes::ComputeDriverService::new_in_process(driver); - Ok(openshell_server::ComputeDriverInstance::InProcess( - std::sync::Arc::new(driver), + let driver = std::sync::Arc::new( + openshell_driver_kubernetes::ComputeDriverService::new_in_process(driver), + ); + Ok(( + openshell_server::ComputeDriverInstance::InProcess(driver.clone()), + Some(driver), )) } } diff --git a/crates/openshell-otel/src/grpc.rs b/crates/openshell-otel/src/grpc.rs index e22af83968..a86ceffca4 100644 --- a/crates/openshell-otel/src/grpc.rs +++ b/crates/openshell-otel/src/grpc.rs @@ -63,6 +63,8 @@ where } pub const COMPUTE_DRIVER_RPC_SERVICE: &str = "openshell.compute.v1.ComputeDriver"; +pub const COMPUTE_DRIVER_IMAGE_UPDATES_RPC_SERVICE: &str = + "openshell.compute.v1.ComputeDriverImageUpdates"; /// Low-cardinality semantic-convention identity for a compute-driver RPC. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -102,6 +104,16 @@ pub mod rpc { "CreateSandbox", "openshell.compute.v1.ComputeDriver/CreateSandbox", ); + pub const UPDATE_SANDBOX_CONFIGURATION: ComputeDriverRpc = ComputeDriverRpc { + service: "openshell.compute.v1.ComputeDriverConfigurationUpdates", + method: "UpdateSandboxConfiguration", + operation: "openshell.compute.v1.ComputeDriverConfigurationUpdates/UpdateSandboxConfiguration", + }; + pub const UPDATE_SANDBOX_IMAGE: ComputeDriverRpc = ComputeDriverRpc { + service: super::COMPUTE_DRIVER_IMAGE_UPDATES_RPC_SERVICE, + method: "UpdateSandboxImage", + operation: "openshell.compute.v1.ComputeDriverImageUpdates/UpdateSandboxImage", + }; pub const GET_SANDBOX: ComputeDriverRpc = ComputeDriverRpc::new( "GetSandbox", "openshell.compute.v1.ComputeDriver/GetSandbox", @@ -190,6 +202,8 @@ pub fn compute_driver_rpc_operation(path: &str) -> Option { Some("GetCapabilities") => Some(rpc::GET_CAPABILITIES), Some("ValidateSandboxCreate") => Some(rpc::VALIDATE_SANDBOX_CREATE), Some("CreateSandbox") => Some(rpc::CREATE_SANDBOX), + Some("UpdateSandboxConfiguration") => Some(rpc::UPDATE_SANDBOX_CONFIGURATION), + Some("UpdateSandboxImage") => Some(rpc::UPDATE_SANDBOX_IMAGE), Some("GetSandbox") => Some(rpc::GET_SANDBOX), Some("ListSandboxes") => Some(rpc::LIST_SANDBOXES), Some("StopSandbox") => Some(rpc::STOP_SANDBOX), @@ -312,6 +326,7 @@ mod tests { rpc::GET_CAPABILITIES, rpc::VALIDATE_SANDBOX_CREATE, rpc::CREATE_SANDBOX, + rpc::UPDATE_SANDBOX_IMAGE, rpc::GET_SANDBOX, rpc::LIST_SANDBOXES, rpc::STOP_SANDBOX, diff --git a/crates/openshell-sdk/src/client.rs b/crates/openshell-sdk/src/client.rs index 69b75ffbd2..59f857adee 100644 --- a/crates/openshell-sdk/src/client.rs +++ b/crates/openshell-sdk/src/client.rs @@ -12,7 +12,7 @@ use crate::auth::{BearerSlot, EdgeAuthInterceptor, bearer_metadata}; use crate::config::{AuthConfig, ClientConfig}; use crate::error::{Result, SdkError}; use crate::pagination::{Page, Pager}; -use crate::raw::AuthedGrpcClient; +use crate::raw::{AuthedGrpcClient, AuthedSandboxUpdatesClient}; use crate::refresh::{RefreshedToken, TokenSource}; use crate::types::{ DeleteOptions, DeletionResult, ExecOptions, ExecResult, Health, ListOptions, SandboxPhase, @@ -116,6 +116,23 @@ impl OpenShellClient { Ok(self.raw_grpc()) } + /// Authenticated client for the optional `SandboxUpdates` service, sharing + /// this client's channel and live bearer slot. Like [`Self::raw_grpc`], this + /// does not refresh or retry OIDC credentials by itself. + pub fn raw_sandbox_updates(&self) -> AuthedSandboxUpdatesClient { + proto::sandbox_updates_client::SandboxUpdatesClient::with_interceptor( + self.channel.clone(), + self.interceptor.clone(), + ) + } + + /// Like [`Self::raw_sandbox_updates`], but refreshes the bearer token first + /// when needed. Reactive retry remains the raw caller's responsibility. + pub async fn raw_sandbox_updates_fresh(&self) -> Result { + self.ensure_fresh().await?; + Ok(self.raw_sandbox_updates()) + } + /// Force an OIDC refresh and write the new token into the live bearer /// slot, regardless of expiry. Returns `true` when a refresher is wired /// and a fresh token was minted, `false` for static auth. Use after a diff --git a/crates/openshell-sdk/src/raw.rs b/crates/openshell-sdk/src/raw.rs index dd3b7b165b..ac9d894d10 100644 --- a/crates/openshell-sdk/src/raw.rs +++ b/crates/openshell-sdk/src/raw.rs @@ -20,6 +20,7 @@ pub use openshell_core::proto; pub use openshell_core::proto::open_shell_client::OpenShellClient as GrpcClient; +pub use openshell_core::proto::sandbox_updates_client::SandboxUpdatesClient; pub use openshell_core::proto::{ CreateSandboxRequest, CreateSandboxTemplateRequest, CreateWorkspaceRequest, DeleteSandboxRequest, DeleteSandboxTemplateRequest, DeleteWorkspaceRequest, ExecSandboxRequest, @@ -28,11 +29,12 @@ pub use openshell_core::proto::{ ListSandboxTemplatesRequest, ListSandboxesRequest, ListWorkspacesRequest, ProviderDesiredIdentity, ProviderMutationKind, ProviderMutationReceipt, ProviderReadinessObservation, ProviderReadinessReason, ProviderReadinessState, - ProviderReadinessStatus, Sandbox, SandboxPhase as ProtoSandboxPhase, SandboxResources, - SandboxServiceLevel, SandboxSpec as ProtoSandboxSpec, SandboxStartup, SandboxTemplate, - SandboxTemplateResponse, SandboxWorkloadConfig, SandboxWorkloadTemplate, - SandboxWorkloadTemplateProvenance, SandboxWorkloadTemplateSpec, - ServiceStatus as ProtoServiceStatus, StartSandboxRequest, StopSandboxRequest, Workspace, + ProviderReadinessStatus, Sandbox, SandboxCommandUpdate, SandboxMapUpdate, + SandboxPhase as ProtoSandboxPhase, SandboxResources, SandboxServiceLevel, + SandboxSpec as ProtoSandboxSpec, SandboxStartup, SandboxTemplate, SandboxTemplateResponse, + SandboxWorkloadConfig, SandboxWorkloadTemplate, SandboxWorkloadTemplateProvenance, + SandboxWorkloadTemplateSpec, ServiceStatus as ProtoServiceStatus, StartSandboxRequest, + StopSandboxRequest, UpdateSandboxRequest, Workspace, }; /// Type alias for the gRPC client wrapped in the SDK's auth interceptor. @@ -42,3 +44,12 @@ pub type AuthedGrpcClient = GrpcClient< crate::EdgeAuthInterceptor, >, >; + +/// Sandbox-updates client using the same live auth slot and transport as the +/// main service client. +pub type AuthedSandboxUpdatesClient = SandboxUpdatesClient< + tonic::service::interceptor::InterceptedService< + tonic::transport::Channel, + crate::EdgeAuthInterceptor, + >, +>; diff --git a/crates/openshell-sdk/tests/client_mock.rs b/crates/openshell-sdk/tests/client_mock.rs index 56ea582109..b6512e326f 100644 --- a/crates/openshell-sdk/tests/client_mock.rs +++ b/crates/openshell-sdk/tests/client_mock.rs @@ -10,6 +10,7 @@ use openshell_core::proto; use openshell_core::proto::open_shell_server::{OpenShell, OpenShellServer}; +use openshell_core::proto::sandbox_updates_server::{SandboxUpdates, SandboxUpdatesServer}; use openshell_sdk::{ AuthConfig, ClientConfig, ExecOptions, ListOptions, OpenShellClient, Refresh, RefreshError, RefreshedToken, SandboxPhase, SandboxSpec, SandboxTemplateCreateSpec, @@ -57,6 +58,7 @@ struct MockState { last_delete_workspace: Mutex>, last_stop: Mutex>, last_start: Mutex>, + last_update: Mutex>, last_list_request: Mutex>, list_requests: Mutex>, last_exec_request: Mutex>, @@ -1108,16 +1110,40 @@ impl OpenShell for TestOpenShell { } } +#[tonic::async_trait] +impl SandboxUpdates for TestOpenShell { + async fn update_sandbox( + &self, + request: tonic::Request, + ) -> Result, Status> { + if let Some(expected) = &self.state.require_bearer { + let got = request + .metadata() + .get("authorization") + .and_then(|value| value.to_str().ok()); + if got != Some(expected.as_str()) { + return Err(Status::unauthenticated("invalid or missing bearer token")); + } + } + *self.state.last_update.lock().await = Some(request.into_inner()); + Ok(Response::new(proto::SandboxResponse::default())) + } +} + /// Spin up the mock gateway, return its endpoint URL. async fn start_mock(state: Arc) -> String { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); let endpoint = format!("http://{addr}"); let stream = TcpListenerStream::new(listener); + let updates = SandboxUpdatesServer::new(TestOpenShell { + state: state.clone(), + }); let svc = OpenShellServer::new(TestOpenShell { state }); tokio::spawn(async move { let _ = tonic::transport::Server::builder() .add_service(svc) + .add_service(updates) .serve_with_incoming(stream) .await; }); @@ -2129,6 +2155,65 @@ async fn raw_grpc_fresh_refreshes_before_raw_call() { ); } +#[tokio::test] +async fn raw_sandbox_updates_shares_transport_and_live_auth_slot() { + let state = Arc::new(MockState { + require_bearer: Some("Bearer fresh-token".to_string()), + ..Default::default() + }); + let endpoint = start_mock(state.clone()).await; + let calls = Arc::new(AtomicU32::new(0)); + let mut config = ClientConfig::new(&endpoint); + config.auth = Some(AuthConfig::Oidc { + token: "stale-token".to_string(), + expires_at: Some( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() + + 5, + ), + refresh: Some(Arc::new(OneShotRefresher { + calls: calls.clone(), + })), + }); + let client = OpenShellClient::connect(config).await.unwrap(); + let mut updates = client.raw_sandbox_updates(); + let request = proto::UpdateSandboxRequest { + workspace_scope: Some(proto::workspace_selector("team")), + name: "sandbox".into(), + image: Some("example/workload:new".into()), + expected_resource_version: 42, + request_id: "550e8400-e29b-41d4-a716-446655440000".into(), + ..Default::default() + }; + assert_eq!( + updates + .update_sandbox(request.clone()) + .await + .unwrap_err() + .code(), + tonic::Code::Unauthenticated + ); + assert_eq!(calls.load(Ordering::SeqCst), 0); + client + .raw_sandbox_updates_fresh() + .await + .unwrap() + .update_sandbox(request.clone()) + .await + .unwrap(); + assert_eq!(calls.load(Ordering::SeqCst), 1); + // Previously created raw clients read the same refreshed bearer slot. + updates.update_sandbox(request.clone()).await.unwrap(); + client + .raw_grpc() + .health(proto::HealthRequest {}) + .await + .unwrap(); + assert_eq!(*state.last_update.lock().await, Some(request)); +} + #[tokio::test] async fn watch_logs_forwards_logs_and_warnings() { let state = Arc::new(MockState { diff --git a/crates/openshell-sdk/tests/sandbox_status_compatibility.rs b/crates/openshell-sdk/tests/sandbox_status_compatibility.rs new file mode 100644 index 0000000000..7a080a381f --- /dev/null +++ b/crates/openshell-sdk/tests/sandbox_status_compatibility.rs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Compile-time source-compatibility fixture for the v0.1.5 public status shape. + +use openshell_sdk::raw::proto::SandboxStatus; + +#[test] +fn baseline_exhaustive_sandbox_status_literal_still_compiles() { + // Intentionally no `..Default::default()`: adding even an optional public + // field breaks existing downstream Rust exhaustive struct literals. + let status = SandboxStatus { + agent_pod: String::new(), + agent_fd: String::new(), + sandbox_fd: String::new(), + conditions: Vec::new(), + phase: 0, + current_policy_version: 0, + main_process_instance_id: String::new(), + exit_code: None, + endpoint_statuses: Vec::new(), + configuration_admission: None, + configuration_activated: None, + provisioning: None, + restart_count: 0, + next_restart_time: None, + main_process_started_time: None, + last_token_grant_failure: None, + }; + assert_eq!(status, SandboxStatus::default()); +} diff --git a/crates/openshell-server/README.md b/crates/openshell-server/README.md index 58f735c35c..1e0c63df46 100644 --- a/crates/openshell-server/README.md +++ b/crates/openshell-server/README.md @@ -18,6 +18,29 @@ Retries reuse that generation and session, including after gateway restart. Credential renewal retains both; credentials for an older launch cannot authorize the new one. +## Optional in-process sandbox updates + +`SharedComputeDriver` retains the generated baseline compute-driver trait, and +`ComputeDriverInstance` retains its existing variants. Existing compiled drivers +and factories need no new trait implementation or registration method. + +A supporting factory overrides `ComputeDriverFactory::build_with_image_updates` +to return its ordinary instance plus a separately registered image updater. The +default calls the existing `build` method and returns no updater. The gateway +requires both this opt-in and the negotiated capability before recording image +intent. Remote drivers register the separate `ComputeDriverImageUpdates` service +on their existing endpoint instead. + +Command, environment, label, and annotation selections use the same stopped-only +transaction and durable interlock. Extended requests require the separate +configuration-update driver capability and RPC; unsupported combinations fail +before admission. User metadata replacement preserves system domains, and labels +are indexed in the same SQL transaction as the payload. The existing activation +flag now requires deployment protocol 2. Old private image-only intents remain +readable, and completion verifies the selected workload targets and labels before +clearing the fence. Annotation replacement commits at acceptance; completion +preserves later annotation writes accepted through `UpdateConfig`. + ## Operator credential export `GetProviderCredentials` exports selected profile-declared runtime environment diff --git a/crates/openshell-server/build.rs b/crates/openshell-server/build.rs index c061cb0b6d..6230f8d259 100644 --- a/crates/openshell-server/build.rs +++ b/crates/openshell-server/build.rs @@ -30,8 +30,10 @@ fn main() -> Result<(), Box> { let storage_proto_dir = manifest_dir.join("proto"); let public_proto_dir = manifest_dir.join("../../proto"); let storage_proto = storage_proto_dir.join("storage.proto"); + let image_proto = storage_proto_dir.join("sandbox_image.proto"); println!("cargo:rerun-if-changed={}", storage_proto.display()); + println!("cargo:rerun-if-changed={}", image_proto.display()); for imported_proto in [ "datamodel.proto", "openshell.proto", @@ -71,7 +73,7 @@ fn main() -> Result<(), Box> { "::openshell_core::proto::sandbox::v1", ) .file_descriptor_set_path(&descriptor_path) - .compile_with_config(proto_config, &[storage_proto], &include_dirs)?; + .compile_with_config(proto_config, &[storage_proto, image_proto], &include_dirs)?; Ok(()) } diff --git a/crates/openshell-server/proto/sandbox_image.proto b/crates/openshell-server/proto/sandbox_image.proto new file mode 100644 index 0000000000..1ed4a2167f --- /dev/null +++ b/crates/openshell-server/proto/sandbox_image.proto @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +syntax = "proto3"; + +// A new private format; frozen openshell.storage.v1 remains unchanged. +package openshell.storage.image.v1; +import "openshell.proto"; + +// Gateway-private image mutation state. Never embed this in public SandboxStatus: +// older gateways may rewrite public protobufs without retaining unknown fields. +message StoredSandboxImageUpdate { + string operation_id = 1; + string previous_image = 2; + string image = 3; + uint64 revision = 4; + // Absent for legacy image-only operations. Presence retains selection semantics. + openshell.v1.SandboxCommandUpdate command = 5; + openshell.v1.SandboxMapUpdate environment = 6; + openshell.v1.SandboxMapUpdate labels = 7; + openshell.v1.SandboxMapUpdate annotations = 8; +} + +message StoredSandboxImageState { + string sandbox_id = 1; + uint64 revision = 2; + StoredSandboxImageUpdate pending = 3; + // Original presence and attempt identity while the interlock is held. + // Completion preserves newer diagnostics and clock edits on the live record. + openshell.v1.SandboxProvisioning previous_provisioning = 4; +} diff --git a/crates/openshell-server/src/cli.rs b/crates/openshell-server/src/cli.rs index 5b2ce01c13..4a04f1a0fc 100644 --- a/crates/openshell-server/src/cli.rs +++ b/crates/openshell-server/src/cli.rs @@ -303,6 +303,11 @@ struct RunArgs { action = ArgAction::Set )] enable_websocket_tunnel: bool, + + /// Activate stopped-sandbox configuration updates only after retiring every incompatible gateway + /// and settling its outstanding driver operations. See HA upgrade guidance. + #[arg(long, env = "OPENSHELL_ENABLE_SANDBOX_UPDATES", default_value_t = false, action = ArgAction::Set)] + enable_sandbox_updates: bool, } pub fn command() -> Command { @@ -570,6 +575,7 @@ fn prepare_server_config_with_drivers( .with_server_sans(args.server_sans.clone()) .with_loopback_service_http(args.enable_loopback_service_http) .with_websocket_tunnel(args.enable_websocket_tunnel); + config.enable_sandbox_updates = args.enable_sandbox_updates; if let Some(sources) = file .as_ref() .and_then(|file| file.openshell.gateway.provider_profile_sources.clone()) @@ -1262,6 +1268,11 @@ fn merge_file_into_args(args: &mut RunArgs, file: &GatewayFileSection, matches: { args.enable_websocket_tunnel = enabled; } + if let Some(enabled) = file.enable_sandbox_updates + && arg_defaulted(matches, "enable_sandbox_updates") + { + args.enable_sandbox_updates = enabled; + } if let Some(mtls_auth) = &file.mtls_auth && arg_defaulted(matches, "enable_mtls_auth") { @@ -2716,6 +2727,40 @@ mod tests { toml::from_str(toml).expect("valid TOML in test fixture") } + #[test] + fn sandbox_update_activation_defaults_off_and_file_requires_explicit_opt_in() { + Environment::new().remove("OPENSHELL_ENABLE_SANDBOX_UPDATES").run(|| { + let (mut args, matches) = parse_with_args(&["openshell-gateway"]); + assert!(!args.enable_sandbox_updates); + assert!(!openshell_core::Config::new(None).enable_sandbox_updates); + let file = config_file_from_toml("[openshell]\nversion = 2\n[openshell.gateway]\nenable_sandbox_updates = true\n"); + merge_file_into_args(&mut args, &file.openshell.gateway, &matches); + assert!(args.enable_sandbox_updates); + }); + } + + #[test] + fn explicit_cli_or_environment_disable_overrides_file_activation() { + let file = config_file_from_toml( + "[openshell]\nversion = 2\n[openshell.gateway]\nenable_sandbox_updates = true\n", + ); + Environment::new() + .remove("OPENSHELL_ENABLE_SANDBOX_UPDATES") + .run(|| { + let (mut args, matches) = + parse_with_args(&["openshell-gateway", "--enable-sandbox-updates", "false"]); + merge_file_into_args(&mut args, &file.openshell.gateway, &matches); + assert!(!args.enable_sandbox_updates); + }); + Environment::new() + .set("OPENSHELL_ENABLE_SANDBOX_UPDATES", "false") + .run(|| { + let (mut args, matches) = parse_with_args(&["openshell-gateway"]); + merge_file_into_args(&mut args, &file.openshell.gateway, &matches); + assert!(!args.enable_sandbox_updates); + }); + } + #[test] fn rejects_legacy_drivers_flag() { let error = command() diff --git a/crates/openshell-server/src/compute/image_update.rs b/crates/openshell-server/src/compute/image_update.rs new file mode 100644 index 0000000000..54e2166a08 --- /dev/null +++ b/crates/openshell-server/src/compute/image_update.rs @@ -0,0 +1,1546 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Stopped-only configuration changes with private durable intent and an operation +//! interlock understood by supported older gateways. + +use super::{ComputeRuntime, provisioning_operation, sandbox_compute_runtime_identity}; +use crate::storage_proto::StoredSandboxImageUpdate as SandboxImageUpdate; +use openshell_core::extension_protocol::COMPUTE_UPDATE_SANDBOX_IMAGE; +use openshell_core::proto::compute::v1::UpdateSandboxImageRequest; +use openshell_core::proto::{Sandbox, SandboxPhase}; +use openshell_core::{GetResourceVersion, ObjectId, ObjectName}; +use tonic::{Request, Status}; +use tracing::info; + +impl ComputeRuntime { + pub(crate) fn with_sandbox_updates(mut self, enabled: bool) -> Self { + self.sandbox_updates_enabled = enabled; + self + } + + #[cfg(test)] + pub(crate) async fn update_sandbox_image( + &self, + sandbox_id: &str, + image: String, + expected_resource_version: u64, + ) -> Result { + self.update_sandbox_configuration( + sandbox_id, + &openshell_core::proto::UpdateSandboxRequest { + image: Some(image), + expected_resource_version, + ..Default::default() + }, + ) + .await + } + + pub(crate) async fn update_sandbox_configuration( + &self, + sandbox_id: &str, + update: &openshell_core::proto::UpdateSandboxRequest, + ) -> Result { + let expected_resource_version = update.expected_resource_version; + let extended = update.command.is_some() + || update.environment.is_some() + || update.labels.is_some() + || update.annotations.is_some(); + let capability_required = if extended { + openshell_core::extension_protocol::COMPUTE_UPDATE_SANDBOX_CONFIGURATION + } else { + COMPUTE_UPDATE_SANDBOX_IMAGE + }; + // Explicit operator activation follows retirement of incompatible + // gateways and their outstanding driver work. Refuse before preflight + // or durable effects; a cached placement ring is not a version proof. + if !self.sandbox_updates_enabled { + return Err(Status::failed_precondition( + "sandbox updates are disabled; complete the compatible deployment rollout before activating them", + )); + } + self.store + .ensure_image_update_deployment_compatible() + .await + .map_err(|error| match error { + crate::persistence::PersistenceError::Config(message) => { + Status::failed_precondition(message) + } + _ => Status::unavailable("cannot verify image-update deployment compatibility"), + })?; + if !self + .driver_info + .negotiated_extension + .supported_capabilities + .iter() + .any(|capability| capability == capability_required) + { + return Err(Status::unimplemented( + "compute driver does not support storage-preserving sandbox image updates", + )); + } + if self.image_update_driver.is_none() { + return Err(Status::unimplemented( + "compute driver has no registered image updater", + )); + } + let lifecycle_guard = self.lifecycle_gates.lock_for(sandbox_id).await; + let sandbox_guard = self.lock_sandbox_for_lifecycle(&lifecycle_guard).await; + let current = self + .store + .get_message::(sandbox_id) + .await + .map_err(|_| Status::internal("fetch sandbox failed"))? + .ok_or_else(|| Status::not_found("sandbox not found"))?; + ensure_image_update_stopped(¤t)?; + if current.get_resource_version() != expected_resource_version { + return Err(crate::grpc::persistence_error_to_status( + crate::persistence::PersistenceError::Conflict { + current_resource_version: Some(current.get_resource_version()), + }, + "update sandbox image", + )); + } + let state = self + .store + .get_sandbox_image_state(sandbox_id) + .await + .map_err(|_| Status::internal("fetch private sandbox image state failed"))?; + let pending = state.as_ref().and_then(|state| state.pending.clone()); + let image = update + .image + .clone() + .or_else(|| { + current + .spec + .as_ref() + .and_then(|spec| spec.template.as_ref()) + .map(|template| template.image.clone()) + }) + .ok_or_else(|| Status::failed_precondition("sandbox has no workload image"))?; + if let Some(intent) = &pending { + ensure_image_update_interlock(¤t, intent)?; + if intent.image != image + || intent.command != update.command + || intent.environment != update.environment + || intent.labels != update.labels + || intent.annotations != update.annotations + { + return Err(Status::failed_precondition( + "another sandbox update is pending; retry the same selected fields and target values", + )); + } + } else { + provisioning_operation::ensure_operation_settled(¤t)?; + } + let old_image = current + .spec + .as_ref() + .and_then(|spec| spec.template.as_ref()) + .map(|template| template.image.clone()) + .filter(|image| !image.is_empty()) + .ok_or_else(|| Status::failed_precondition("sandbox has no workload image"))?; + let mut candidate = current.clone(); + openshell_core::sandbox_update::apply_configuration( + &mut candidate, + update.command.as_ref(), + update.environment.as_ref(), + update.labels.as_ref(), + update.annotations.as_ref(), + ); + if pending.is_none() && old_image == image && candidate == current { + return Ok(current); + } + let revision = state.as_ref().map_or(0, |state| state.revision); + let intent = pending.clone().unwrap_or_else(|| SandboxImageUpdate { + operation_id: uuid::Uuid::new_v4().to_string(), + previous_image: old_image, + image, + revision: revision.saturating_add(1), + command: update.command.clone(), + environment: update.environment.clone(), + labels: update.labels.clone(), + annotations: update.annotations.clone(), + }); + if pending.is_none() && revision == u64::MAX { + return Err(Status::resource_exhausted( + "sandbox image revision exhausted", + )); + } + let accepted = if pending.is_some() { + current + } else { + self.reconcile_sandbox_update(¤t, &intent, true) + .await?; + // The private intent, selected configuration, and OLD-understood pending + // operation flag commit in one version-fenced transaction. + self.store + .begin_sandbox_image_update( + sandbox_id, + expected_resource_version, + revision, + &intent, + ) + .await + .map_err(|error| { + crate::grpc::persistence_error_to_status(error, "record sandbox image update") + })? + }; + self.sandbox_index.update_from_sandbox(&accepted); + self.sandbox_watch_bus.notify(sandbox_id); + drop(sandbox_guard); + let runtime = self.clone(); + tokio::spawn(async move { + let _lifecycle_guard = lifecycle_guard; + runtime.complete_image_update(accepted, intent).await + }) + .await + .map_err(|_| { + Status::internal("sandbox image update worker failed; intent remains pending") + })? + } + + async fn complete_image_update( + &self, + pending: Sandbox, + intent: SandboxImageUpdate, + ) -> Result { + ensure_image_update_interlock(&pending, &intent)?; + self.reconcile_sandbox_update(&pending, &intent, false) + .await?; + let _guard = self.lock_sandbox_local(pending.object_id()).await; + // An ambiguous error NEVER clears either the private intent or the + // public operation interlock. Only exact successful reconciliation can. + let updated = self + .store + .complete_sandbox_image_update(pending.object_id(), &intent) + .await + .map_err(|error| { + crate::grpc::persistence_error_to_status(error, "complete sandbox image update") + })?; + self.sandbox_index.update_from_sandbox(&updated); + self.sandbox_watch_bus.notify(updated.object_id()); + info!(sandbox_id = %updated.object_id(), operation_id = %intent.operation_id, + "Sandbox configuration updated; sandbox remains stopped"); + Ok(updated) + } + + async fn reconcile_sandbox_update( + &self, + sandbox: &Sandbox, + intent: &SandboxImageUpdate, + validate_only: bool, + ) -> Result<(), Status> { + let image_update = image_update_driver_request(sandbox, intent, validate_only); + if intent.command.is_none() + && intent.environment.is_none() + && intent.labels.is_none() + && intent.annotations.is_none() + { + self.call_image_update_driver(image_update).await?; + return Ok(()); + } + let mut candidate = sandbox.clone(); + openshell_core::sandbox_update::apply_configuration( + &mut candidate, + intent.command.as_ref(), + intent.environment.as_ref(), + intent.labels.as_ref(), + intent.annotations.as_ref(), + ); + let spec = candidate + .spec + .as_ref() + .ok_or_else(|| Status::failed_precondition("sandbox has no spec"))?; + let main_process = intent.command.as_ref().map(|_| { + openshell_core::proto::compute::v1::SandboxMainProcessUpdate { + command: spec.command.clone(), + tty: spec.tty, + } + }); + let environment = intent.environment.as_ref().map(|_| { + let mut values = spec + .template + .as_ref() + .map_or_else(Default::default, |template| template.environment.clone()); + values.extend(spec.environment.clone()); + openshell_core::proto::compute::v1::SandboxEnvironmentUpdate { values } + }); + let request = openshell_core::proto::compute::v1::UpdateSandboxConfigurationRequest { + image_update: Some(image_update), + main_process, + environment, + }; + let driver = self.image_update_driver.clone().ok_or_else(|| { + Status::unimplemented("compute driver has no registered configuration updater") + })?; + self.driver + .call_owned( + openshell_otel::rpc::UPDATE_SANDBOX_CONFIGURATION, + Some(sandbox.object_id()), + move |_| async move { + driver + .update_sandbox_configuration(Request::new(request)) + .await + }, + ) + .await?; + Ok(()) + } + + async fn call_image_update_driver( + &self, + request: UpdateSandboxImageRequest, + ) -> Result< + tonic::Response, + Status, + > { + let driver = self.image_update_driver.clone().ok_or_else(|| { + Status::unimplemented("compute driver has no registered image updater") + })?; + let sandbox_id = request.sandbox_id.clone(); + self.driver + .call_owned( + openshell_otel::rpc::UPDATE_SANDBOX_IMAGE, + Some(&sandbox_id), + move |_baseline| async move { + driver.update_sandbox_image(Request::new(request)).await + }, + ) + .await + } + + pub(super) async fn ensure_no_pending_image_update( + &self, + sandbox: &Sandbox, + ) -> Result<(), Status> { + let state = self + .store + .get_sandbox_image_state(sandbox.object_id()) + .await + .map_err(|_| Status::unavailable("cannot read private sandbox image state"))?; + if state.as_ref().is_some_and(|state| state.pending.is_some()) { + return Err(Status::failed_precondition( + "sandbox update is pending; read the current resource version and retry UpdateSandbox with the same selected fields and target values before starting", + )); + } + Ok(()) + } +} + +fn ensure_image_update_interlock( + sandbox: &Sandbox, + intent: &SandboxImageUpdate, +) -> Result<(), Status> { + if sandbox + .status + .as_ref() + .and_then(|status| status.provisioning.as_ref()) + .is_none_or(|record| { + !record.driver_operation_pending || record.driver_operation_id != intent.operation_id + }) + { + return Err(Status::aborted( + "sandbox image-update operation interlock changed", + )); + } + Ok(()) +} + +fn image_update_driver_request( + sandbox: &Sandbox, + intent: &SandboxImageUpdate, + validate_only: bool, +) -> UpdateSandboxImageRequest { + UpdateSandboxImageRequest { + sandbox_id: sandbox.object_id().to_string(), + name: sandbox.object_name().to_string(), + image: intent.image.clone(), + expected_image: intent.previous_image.clone(), + expected_runtime_identity: sandbox_compute_runtime_identity(sandbox), + operation_id: intent.operation_id.clone(), + validate_only, + revision: intent.revision, + } +} + +fn ensure_image_update_stopped(sandbox: &Sandbox) -> Result<(), Status> { + if sandbox.phase() != SandboxPhase::Stopped as i32 + || sandbox + .metadata + .as_ref() + .is_some_and(|metadata| metadata.deletion_time.is_some()) + { + return Err(Status::failed_precondition( + "sandbox must be Stopped before updating its image", + )); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::compute::{ + COMPUTE_RUNTIME_IDENTITY_ANNOTATION, apply_driver_snapshot, provisioning_deadline, + }; + use crate::sandbox_index::SandboxIndex; + use crate::sandbox_watch::SandboxWatchBus; + use crate::supervisor_session::SupervisorSessionRegistry; + use crate::test_support::{FakeComputeDriver, FakeComputeDriverCall}; + use crate::tracing_bus::TracingLogBus; + use openshell_core::proto::compute::v1::{DriverSandbox, DriverSandboxStatus}; + use openshell_core::proto::datamodel::v1::ObjectMeta; + use openshell_core::proto::{SandboxSpec, SandboxTemplate}; + use std::collections::HashMap; + use std::sync::Arc; + use tonic::Code; + + async fn fixture(supported: bool) -> (ComputeRuntime, Arc, Sandbox) { + let store = Arc::new(crate::persistence::test_store().await); + crate::ensure_default_workspace(&store).await.unwrap(); + let driver = Arc::new(FakeComputeDriver::new()); + let mut runtime = ComputeRuntime::from_driver( + "test".into(), + driver.clone(), + None, + store.clone(), + SandboxIndex::new(), + SandboxWatchBus::new(), + TracingLogBus::new(), + Arc::new(SupervisorSessionRegistry::new()), + ) + .await + .unwrap(); + runtime.sandbox_updates_enabled = true; + if supported { + runtime.image_update_driver = Some(driver.clone()); + runtime + .driver_info + .negotiated_extension + .supported_capabilities + .extend([ + COMPUTE_UPDATE_SANDBOX_IMAGE.into(), + openshell_core::extension_protocol::COMPUTE_UPDATE_SANDBOX_CONFIGURATION.into(), + ]); + } + let mut sandbox = Sandbox { + metadata: Some(ObjectMeta { + id: "image-sandbox-id".into(), + name: "image-sandbox".into(), + workspace: "default".into(), + labels: HashMap::from([("app".into(), "retained".into())]), + annotations: HashMap::from([ + ( + COMPUTE_RUNTIME_IDENTITY_ANNOTATION.into(), + "stable-runtime".into(), + ), + ( + crate::compute::ISOLATION_BACKEND_ANNOTATION.into(), + openshell_core::isolation_registration::BUILTIN_BACKEND_NAME.into(), + ), + ]), + ..Default::default() + }), + spec: Some(SandboxSpec { + template: Some(SandboxTemplate { + image: "example/workload:old".into(), + ..Default::default() + }), + environment: HashMap::from([("KEEP".into(), "value".into())]), + command: vec!["/bin/sh".into()], + ..Default::default() + }), + host_key_fingerprint: "SHA256:stable-key".into(), + ..Default::default() + }; + sandbox.set_phase(SandboxPhase::Stopped as i32); + store.put_message(&sandbox).await.unwrap(); + let sandbox = store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + (runtime, driver, sandbox) + } + + fn image_calls(driver: &FakeComputeDriver) -> Vec { + driver + .calls() + .into_iter() + .filter_map(|call| match call { + FakeComputeDriverCall::UpdateSandboxImage { request } if !request.validate_only => { + Some(request) + } + _ => None, + }) + .collect() + } + + #[tokio::test] + async fn configuration_update_reconciles_all_fields_and_recovers_exact_pending_target() { + use openshell_core::proto::{SandboxCommandUpdate, SandboxMapUpdate, UpdateSandboxRequest}; + let (runtime, driver, original) = fixture(true).await; + let request = UpdateSandboxRequest { + image: Some("example/workload:new".into()), + command: Some(SandboxCommandUpdate { + argv: vec!["/usr/local/bin/start-agent".into(), "--new".into()], + }), + environment: Some(SandboxMapUpdate { + values: HashMap::from([("MODE".into(), "new".into())]), + }), + labels: Some(SandboxMapUpdate { + values: HashMap::from([("team".into(), "agents".into())]), + }), + annotations: Some(SandboxMapUpdate { + values: HashMap::from([("example.com/revision".into(), "2".into())]), + }), + expected_resource_version: original.get_resource_version(), + ..Default::default() + }; + driver.set_image_update_error(Some(Code::Unavailable)); + assert_eq!( + runtime + .update_sandbox_configuration(original.object_id(), &request) + .await + .unwrap_err() + .code(), + Code::Unavailable + ); + let pending = runtime + .store + .get_message::(original.object_id()) + .await + .unwrap() + .unwrap(); + assert!( + pending + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap() + .driver_operation_pending + ); + let mut retry = request.clone(); + retry.expected_resource_version = pending.get_resource_version(); + retry + .command + .as_mut() + .unwrap() + .argv + .push("--different".into()); + assert_eq!( + runtime + .update_sandbox_configuration(original.object_id(), &retry) + .await + .unwrap_err() + .code(), + Code::FailedPrecondition + ); + retry.command = request.command.clone(); + driver.set_image_update_error(None); + let updated = runtime + .update_sandbox_configuration(original.object_id(), &retry) + .await + .unwrap(); + assert_eq!(updated.phase(), SandboxPhase::Stopped as i32); + assert_eq!( + updated.spec.as_ref().unwrap().command, + request.command.unwrap().argv + ); + assert_eq!( + updated.spec.as_ref().unwrap().environment, + request.environment.unwrap().values + ); + for (key, value) in &original.metadata.as_ref().unwrap().annotations { + if openshell_core::sandbox_update::is_system_metadata_key(key) { + assert_eq!( + updated.metadata.as_ref().unwrap().annotations.get(key), + Some(value) + ); + } + } + assert!( + runtime + .store + .get_sandbox_image_state(original.object_id()) + .await + .unwrap() + .unwrap() + .pending + .is_none() + ); + let clear = UpdateSandboxRequest { + command: Some(SandboxCommandUpdate::default()), + environment: Some(SandboxMapUpdate::default()), + labels: Some(SandboxMapUpdate::default()), + annotations: Some(SandboxMapUpdate::default()), + expected_resource_version: updated.get_resource_version(), + ..Default::default() + }; + let cleared = runtime + .update_sandbox_configuration(original.object_id(), &clear) + .await + .unwrap(); + assert!(cleared.spec.as_ref().unwrap().command.is_empty()); + assert!(cleared.spec.as_ref().unwrap().environment.is_empty()); + assert!(cleared.metadata.as_ref().unwrap().labels.is_empty()); + assert_eq!( + cleared + .spec + .as_ref() + .unwrap() + .template + .as_ref() + .unwrap() + .image, + "example/workload:new" + ); + } + + #[tokio::test] + async fn configuration_update_requires_capability_before_mutating() { + use openshell_core::proto::SandboxCommandUpdate; + let (mut runtime, _, original) = fixture(true).await; + runtime + .driver_info + .negotiated_extension + .supported_capabilities + .retain(|capability| { + capability + != openshell_core::extension_protocol::COMPUTE_UPDATE_SANDBOX_CONFIGURATION + }); + let update = openshell_core::proto::UpdateSandboxRequest { + command: Some(SandboxCommandUpdate::default()), + expected_resource_version: original.get_resource_version(), + ..Default::default() + }; + assert_eq!( + runtime + .update_sandbox_configuration(original.object_id(), &update) + .await + .unwrap_err() + .code(), + Code::Unimplemented + ); + assert!( + runtime + .store + .get_sandbox_image_state(original.object_id()) + .await + .unwrap() + .is_none() + ); + assert_eq!( + runtime + .store + .get_message::(original.object_id()) + .await + .unwrap() + .unwrap(), + original + ); + } + + #[tokio::test] + async fn image_update_preserves_identity_and_every_other_spec_field() { + let (runtime, driver, mut original) = fixture(true).await; + // Image edits must preserve the upstream backend pin and launch identity, + // including generation/session and authorization epoch annotations. + crate::auth::sandbox_session::PersistedSandboxIdentity::new() + .unwrap() + .write(&mut original.metadata.as_mut().unwrap().annotations); + runtime.store.put_message(&original).await.unwrap(); + let original = runtime + .store + .get_message::(original.object_id()) + .await + .unwrap() + .unwrap(); + let updated = runtime + .update_sandbox_image( + original.object_id(), + "example/workload:new".into(), + original.get_resource_version(), + ) + .await + .unwrap(); + let mut expected = original.spec.clone().unwrap(); + expected.template.as_mut().unwrap().image = "example/workload:new".into(); + assert_eq!(updated.spec, Some(expected)); + assert_eq!(updated.object_id(), original.object_id()); + assert_eq!(updated.object_name(), original.object_name()); + assert_eq!(updated.host_key_fingerprint, original.host_key_fingerprint); + assert_eq!( + updated.metadata.as_ref().unwrap().annotations, + original.metadata.as_ref().unwrap().annotations + ); + assert_eq!( + updated.metadata.as_ref().unwrap().labels, + original.metadata.as_ref().unwrap().labels + ); + assert_eq!(updated.phase(), SandboxPhase::Stopped as i32); + let state = runtime + .store + .get_sandbox_image_state(updated.object_id()) + .await + .unwrap() + .unwrap(); + assert!(state.pending.is_none()); + assert_eq!(state.revision, 1); + assert_eq!(updated.status, original.status); + let calls = image_calls(&driver); + assert_eq!(calls.len(), 1); + assert_eq!(calls[0].expected_image, "example/workload:old"); + assert_eq!(calls[0].image, "example/workload:new"); + assert_eq!(calls[0].revision, 1); + assert_eq!(calls[0].expected_runtime_identity, "stable-runtime"); + assert!(uuid::Uuid::parse_str(&calls[0].operation_id).is_ok()); + assert!(!driver.calls().iter().any(|call| matches!( + call, + FakeComputeDriverCall::CreateSandbox { .. } + | FakeComputeDriverCall::DeleteSandbox { .. } + | FakeComputeDriverCall::StartSandbox { .. } + ))); + let unchanged = runtime + .update_sandbox_image( + updated.object_id(), + "example/workload:new".into(), + updated.get_resource_version(), + ) + .await + .unwrap(); + assert_eq!( + unchanged.get_resource_version(), + updated.get_resource_version() + ); + assert_eq!(image_calls(&driver).len(), 1); + let back = runtime + .update_sandbox_image( + updated.object_id(), + "example/workload:old".into(), + updated.get_resource_version(), + ) + .await + .unwrap(); + assert_eq!( + runtime + .store + .get_sandbox_image_state(back.object_id()) + .await + .unwrap() + .unwrap() + .revision, + 2 + ); + assert_eq!(image_calls(&driver)[1].revision, 2); + } + + #[tokio::test] + async fn image_update_state_is_removed_on_sandbox_deletion() { + for out_of_band in [false, true] { + let (runtime, _, original) = fixture(true).await; + let mut unrelated = original.clone(); + let metadata = unrelated.metadata.as_mut().unwrap(); + metadata.id = "unrelated-sandbox-id".into(); + metadata.name = "unrelated-sandbox".into(); + runtime.store.put_message(&unrelated).await.unwrap(); + let unrelated = runtime + .store + .get_message::(unrelated.object_id()) + .await + .unwrap() + .unwrap(); + for sandbox in [&original, &unrelated] { + runtime + .update_sandbox_image( + sandbox.object_id(), + "example/workload:new".into(), + sandbox.get_resource_version(), + ) + .await + .unwrap(); + } + let unrelated_state = runtime + .store + .get_sandbox_image_state(unrelated.object_id()) + .await + .unwrap() + .unwrap(); + assert!( + runtime + .store + .get_sandbox_image_state(original.object_id()) + .await + .unwrap() + .is_some() + ); + + // Exercise explicit deletion and watch-driven removal, including + // a delayed or repeated watch deletion event. + if out_of_band { + runtime.apply_deleted(original.object_id()).await.unwrap(); + } else { + runtime + .delete_sandbox("default", original.object_name()) + .await + .unwrap(); + } + runtime.apply_deleted(original.object_id()).await.unwrap(); + assert!( + runtime + .store + .get_message::(original.object_id()) + .await + .unwrap() + .is_none() + ); + assert!( + runtime + .store + .get_sandbox_image_state(original.object_id()) + .await + .unwrap() + .is_none() + ); + assert_eq!( + runtime + .store + .get_sandbox_image_state(unrelated.object_id()) + .await + .unwrap(), + Some(unrelated_state) + ); + } + } + + #[tokio::test] + async fn image_update_rejects_unregistered_updater_without_writes() { + let (mut runtime, driver, original) = fixture(true).await; + runtime.image_update_driver = None; + driver.clear_calls(); + let error = runtime + .update_sandbox_image( + original.object_id(), + "example/workload:new".into(), + original.get_resource_version(), + ) + .await + .unwrap_err(); + assert_eq!(error.code(), Code::Unimplemented); + assert!(driver.calls().is_empty()); + assert_eq!( + runtime + .store + .get_message::(original.object_id()) + .await + .unwrap(), + Some(original.clone()) + ); + assert!( + runtime + .store + .get_sandbox_image_state(original.object_id()) + .await + .unwrap() + .is_none() + ); + } + + #[tokio::test] + async fn image_update_rejects_unsupported_driver_and_stale_version_without_writes() { + let (runtime, driver, original) = fixture(false).await; + assert_eq!( + runtime + .update_sandbox_image( + original.object_id(), + "example/workload:new".into(), + original.get_resource_version() + ) + .await + .unwrap_err() + .code(), + Code::Unimplemented + ); + assert!(image_calls(&driver).is_empty()); + let (runtime, driver, original) = fixture(true).await; + assert_eq!( + runtime + .update_sandbox_image( + original.object_id(), + "example/workload:new".into(), + original.get_resource_version() + 1 + ) + .await + .unwrap_err() + .code(), + Code::Aborted + ); + assert!(image_calls(&driver).is_empty()); + assert_eq!( + runtime + .store + .get_message::(original.object_id()) + .await + .unwrap() + .unwrap(), + original + ); + } + + #[tokio::test] + async fn failed_image_preflight_has_no_durable_effects() { + let (runtime, driver, original) = fixture(true).await; + driver.set_image_validation_error(Some(Code::InvalidArgument)); + assert_eq!( + runtime + .update_sandbox_image( + original.object_id(), + "invalid:image".into(), + original.get_resource_version() + ) + .await + .unwrap_err() + .code(), + Code::InvalidArgument + ); + assert!(image_calls(&driver).is_empty()); + assert_eq!( + runtime + .store + .get_message::(original.object_id()) + .await + .unwrap() + .unwrap(), + original + ); + } + + #[tokio::test] + async fn concurrent_image_updates_cannot_overwrite_each_other() { + let (runtime, driver, original) = fixture(true).await; + // Real RPCs are separate tasks. Polling both futures with join! in one + // task would share the debug task-local lock-order tracker. + let spawn_update = |image: &str| { + let runtime = runtime.clone(); + let sandbox_id = original.object_id().to_string(); + let version = original.get_resource_version(); + let image = image.to_string(); + tokio::spawn(async move { + runtime + .update_sandbox_image(&sandbox_id, image, version) + .await + }) + }; + let first = spawn_update("example/workload:first"); + let second = spawn_update("example/workload:second"); + let (first, second) = tokio::join!(first, second); + let (first, second) = (first.unwrap(), second.unwrap()); + assert_ne!(first.is_ok(), second.is_ok()); + assert_eq!( + first.err().or_else(|| second.err()).unwrap().code(), + Code::Aborted + ); + assert_eq!(image_calls(&driver).len(), 1); + } + + #[tokio::test] + async fn image_update_rejects_non_stopped_states() { + for phase in [ + SandboxPhase::Ready, + SandboxPhase::Provisioning, + SandboxPhase::Starting, + SandboxPhase::Stopping, + SandboxPhase::Completed, + SandboxPhase::Error, + SandboxPhase::Deleting, + ] { + let (runtime, driver, mut sandbox) = fixture(true).await; + sandbox.set_phase(phase as i32); + runtime.store.put_message(&sandbox).await.unwrap(); + let sandbox = runtime + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!( + runtime + .update_sandbox_image( + sandbox.object_id(), + "example/workload:new".into(), + sandbox.get_resource_version() + ) + .await + .unwrap_err() + .code(), + Code::FailedPrecondition, + "{phase:?}" + ); + assert!(image_calls(&driver).is_empty()); + } + } + + #[tokio::test] + async fn image_update_can_roll_back_after_start_timeout_cleanup() { + let (runtime, driver, mut original) = fixture(true).await; + crate::auth::sandbox_session::PersistedSandboxIdentity::new() + .unwrap() + .write(&mut original.metadata.as_mut().unwrap().annotations); + runtime.store.put_message(&original).await.unwrap(); + let original = runtime + .store + .get_message::(original.object_id()) + .await + .unwrap() + .unwrap(); + let upgraded = runtime + .update_sandbox_image( + original.object_id(), + "example/workload:missing".into(), + original.get_resource_version(), + ) + .await + .unwrap(); + let upgraded_revision = runtime + .store + .get_sandbox_image_state(upgraded.object_id()) + .await + .unwrap() + .unwrap() + .revision; + let starting = runtime + .start_sandbox("default", original.object_name()) + .await + .unwrap(); + assert_eq!(starting.phase(), SandboxPhase::Starting as i32); + // Backdate the owned preparation clock rather than waiting for the + // production budget. Keep its attempt and settled operation identities. + let overdue = runtime + .store + .update_message_cas::( + starting.object_id(), + starting.get_resource_version(), + |sandbox| { + let record = sandbox + .status + .as_mut() + .unwrap() + .provisioning + .as_mut() + .unwrap(); + record.preparation_deadline = + openshell_core::time::timestamp_from_millis(1).ok(); + record.deadline = record.preparation_deadline; + }, + ) + .await + .unwrap(); + let expired = { + let _guard = runtime.lock_sandbox_local(overdue.object_id()).await; + runtime + .claim_provisioning_timeout(&overdue, openshell_core::time::now_ms()) + .await + .unwrap() + .unwrap() + }; + assert_eq!(expired.phase(), SandboxPhase::Error as i32); + assert!( + runtime + .stop_sandbox("default", original.object_name()) + .await + .is_err() + ); + assert!( + runtime + .update_sandbox_image( + original.object_id(), + "example/workload:old".into(), + expired.get_resource_version() + ) + .await + .is_err() + ); + { + let gate = runtime.lifecycle_gates.lock_for(expired.object_id()).await; + runtime + .reclaim_provisioning_timeout(&expired, &gate) + .await + .unwrap(); + } + let reclaimed = runtime + .store + .get_message::(original.object_id()) + .await + .unwrap() + .unwrap(); + assert!( + reclaimed + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap() + .cleanup_completed_time + .is_some() + ); + let stopped = runtime + .stop_sandbox("default", original.object_name()) + .await + .unwrap(); + assert_eq!(stopped.phase(), SandboxPhase::Stopped as i32); + assert_eq!( + stopped.status.as_ref().unwrap().provisioning, + reclaimed.status.as_ref().unwrap().provisioning + ); + let corrected = runtime + .update_sandbox_image( + original.object_id(), + "example/workload:old".into(), + stopped.get_resource_version(), + ) + .await + .unwrap(); + assert_eq!( + runtime + .store + .get_sandbox_image_state(corrected.object_id()) + .await + .unwrap() + .unwrap() + .revision, + upgraded_revision + 1 + ); + assert_eq!(corrected.object_id(), original.object_id()); + assert_eq!( + corrected.host_key_fingerprint, + original.host_key_fingerprint + ); + assert_eq!(corrected.spec, original.spec); + let retried = runtime + .start_sandbox("default", original.object_name()) + .await + .unwrap(); + assert_eq!(retried.phase(), SandboxPhase::Starting as i32); + assert!(!provisioning_deadline::timed_out(&retried)); + let calls = driver.calls(); + assert_eq!( + calls + .iter() + .filter(|call| matches!(call, FakeComputeDriverCall::StopSandbox { .. })) + .count(), + 1, + "only timeout reclamation stops the driver" + ); + assert_eq!( + calls + .iter() + .filter(|call| matches!(call, FakeComputeDriverCall::StartSandbox { .. })) + .count(), + 2 + ); + assert!(!calls.iter().any(|call| matches!( + call, + FakeComputeDriverCall::CreateSandbox { .. } + | FakeComputeDriverCall::DeleteSandbox { .. } + ))); + } + + #[tokio::test] + async fn interrupted_image_update_blocks_start_and_retries_the_same_operation() { + let (runtime, driver, original) = fixture(true).await; + driver.set_image_update_error(Some(Code::Unavailable)); + assert_eq!( + runtime + .update_sandbox_image( + original.object_id(), + "example/workload:new".into(), + original.get_resource_version() + ) + .await + .unwrap_err() + .code(), + Code::Unavailable + ); + let pending = runtime + .store + .get_message::(original.object_id()) + .await + .unwrap() + .unwrap(); + assert!( + runtime + .store + .get_sandbox_image_state(pending.object_id()) + .await + .unwrap() + .unwrap() + .pending + .is_some() + ); + assert_eq!( + runtime + .start_sandbox("default", original.object_name()) + .await + .unwrap_err() + .code(), + Code::FailedPrecondition + ); + assert_eq!( + runtime + .update_sandbox_image( + original.object_id(), + "example/workload:other".into(), + pending.get_resource_version() + ) + .await + .unwrap_err() + .code(), + Code::FailedPrecondition + ); + driver.set_image_update_error(None); + let complete = runtime + .update_sandbox_image( + original.object_id(), + "example/workload:new".into(), + pending.get_resource_version(), + ) + .await + .unwrap(); + assert!( + runtime + .store + .get_sandbox_image_state(complete.object_id()) + .await + .unwrap() + .unwrap() + .pending + .is_none() + ); + let calls = image_calls(&driver); + assert_eq!(calls.len(), 2); + assert_eq!(calls[0], calls[1]); + } + + #[tokio::test] + async fn image_update_intent_survives_driver_status_replacement() { + let (runtime, driver, original) = fixture(true).await; + driver.set_image_update_error(Some(Code::Unavailable)); + runtime + .update_sandbox_image( + original.object_id(), + "example/workload:new".into(), + original.get_resource_version(), + ) + .await + .unwrap_err(); + let expected = runtime + .store + .get_sandbox_image_state(original.object_id()) + .await + .unwrap() + .unwrap(); + let mut sandbox = runtime + .store + .get_message::(original.object_id()) + .await + .unwrap() + .unwrap(); + let grant_failure = Box::new(openshell_core::proto::TokenGrantFailureObservation { + destination_host: "api.example.com".into(), + ..Default::default() + }); + sandbox.status.as_mut().unwrap().last_token_grant_failure = Some(grant_failure.clone()); + let incoming = DriverSandbox { + id: original.object_id().into(), + name: original.object_name().into(), + status: Some(DriverSandboxStatus::default()), + ..Default::default() + }; + apply_driver_snapshot(&mut sandbox, &incoming, false, false); + runtime.store.put_message(&sandbox).await.unwrap(); + assert_eq!( + runtime + .store + .get_sandbox_image_state(original.object_id()) + .await + .unwrap() + .unwrap(), + expected + ); + assert!( + sandbox + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap() + .driver_operation_pending + ); + assert_eq!( + sandbox.status.as_ref().unwrap().last_token_grant_failure, + Some(grant_failure) + ); + assert_eq!( + runtime + .start_sandbox("default", original.object_name()) + .await + .unwrap_err() + .code(), + Code::FailedPrecondition + ); + } + + #[tokio::test] + async fn sandbox_update_activation_is_disabled_by_default_without_effects() { + let (mut runtime, driver, original) = fixture(true).await; + runtime.sandbox_updates_enabled = false; + assert_eq!( + runtime + .update_sandbox_image( + original.object_id(), + "example/workload:new".into(), + original.get_resource_version() + ) + .await + .unwrap_err() + .code(), + Code::FailedPrecondition + ); + assert!( + !driver + .calls() + .iter() + .any(|call| matches!(call, FakeComputeDriverCall::UpdateSandboxImage { .. })) + ); + assert_eq!( + runtime + .store + .get_message::(original.object_id()) + .await + .unwrap() + .unwrap(), + original + ); + assert!( + runtime + .store + .get_sandbox_image_state(original.object_id()) + .await + .unwrap() + .is_none() + ); + } + // These are the relevant pre-feature wire fields: no image-update fields + // exist in public status. Older readers already understand provisioning. + #[derive(Clone, PartialEq, prost::Message)] + struct LegacyStatus014 { + #[prost(int32, tag = "6")] + phase: i32, + #[prost(message, optional, tag = "13")] + provisioning: Option, + } + + #[tokio::test] + async fn legacy_status_roundtrip_cannot_erase_intent_or_bypass_lifecycle_interlock() { + use prost::Message; + let (runtime, driver, original) = fixture(true).await; + driver.set_image_update_error(Some(Code::Unavailable)); + runtime + .update_sandbox_image( + original.object_id(), + "example/workload:new".into(), + original.get_resource_version(), + ) + .await + .unwrap_err(); + let private_before = runtime + .store + .get_sandbox_image_state(original.object_id()) + .await + .unwrap() + .unwrap(); + let mut pending = runtime + .store + .get_message::(original.object_id()) + .await + .unwrap() + .unwrap(); + let legacy = + LegacyStatus014::decode(pending.status.as_ref().unwrap().encode_to_vec().as_slice()) + .unwrap(); + assert!( + legacy + .provisioning + .as_ref() + .unwrap() + .driver_operation_pending + ); + pending.status = Some( + openshell_core::proto::SandboxStatus::decode(legacy.encode_to_vec().as_slice()) + .unwrap(), + ); + runtime.store.put_message(&pending).await.unwrap(); + let rewritten = runtime + .store + .get_message::(original.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!( + runtime + .store + .get_sandbox_image_state(original.object_id()) + .await + .unwrap() + .unwrap(), + private_before + ); + // This existing guard is also present in v0.1.4, unlike our private check. + assert_eq!( + provisioning_operation::ensure_operation_settled(&rewritten) + .unwrap_err() + .code(), + Code::FailedPrecondition + ); + assert_eq!( + runtime + .start_sandbox("default", original.object_name()) + .await + .unwrap_err() + .code(), + Code::FailedPrecondition + ); + assert_eq!( + runtime + .stop_sandbox("default", original.object_name()) + .await + .unwrap_err() + .code(), + Code::FailedPrecondition + ); + assert_eq!( + runtime + .delete_sandbox("default", original.object_name()) + .await + .unwrap_err() + .code(), + Code::FailedPrecondition + ); + runtime + .recover_persisted_lifecycle_transitions() + .await + .unwrap(); + assert!(!driver.calls().iter().any(|call| matches!( + call, + FakeComputeDriverCall::StartSandbox { .. } + | FakeComputeDriverCall::StopSandbox { .. } + | FakeComputeDriverCall::DeleteSandbox { .. } + ))); + driver.set_image_update_error(None); + let complete = runtime + .update_sandbox_image( + original.object_id(), + "example/workload:new".into(), + rewritten.get_resource_version(), + ) + .await + .unwrap(); + assert!(provisioning_operation::ensure_operation_settled(&complete).is_ok()); + assert!( + runtime + .store + .get_sandbox_image_state(original.object_id()) + .await + .unwrap() + .unwrap() + .pending + .is_none() + ); + } + + #[tokio::test] + async fn stale_legacy_start_cas_loses_to_atomic_image_acceptance() { + let (runtime, driver, original) = fixture(true).await; + // Model an old Start paused after reading Stopped but before its CAS. + driver.set_image_update_error(Some(Code::Unavailable)); + runtime + .update_sandbox_image( + original.object_id(), + "example/workload:new".into(), + original.get_resource_version(), + ) + .await + .unwrap_err(); + let stale_start = runtime + .store + .update_message_cas::( + original.object_id(), + original.get_resource_version(), + |sandbox| sandbox.set_phase(SandboxPhase::Starting as i32), + ) + .await; + assert!(matches!( + stale_start, + Err(crate::persistence::PersistenceError::Conflict { .. }) + )); + let current = runtime + .store + .get_message::(original.object_id()) + .await + .unwrap() + .unwrap(); + assert!(provisioning_operation::ensure_operation_settled(¤t).is_err()); + assert!( + !driver + .calls() + .iter() + .any(|call| matches!(call, FakeComputeDriverCall::StartSandbox { .. })) + ); + } + + #[tokio::test] + async fn image_acceptance_loses_if_legacy_start_already_claimed_starting() { + let (runtime, driver, original) = fixture(true).await; + let starting = runtime + .store + .update_message_cas::( + original.object_id(), + original.get_resource_version(), + |sandbox| sandbox.set_phase(SandboxPhase::Starting as i32), + ) + .await + .unwrap(); + assert_eq!( + runtime + .update_sandbox_image( + original.object_id(), + "example/workload:new".into(), + starting.get_resource_version() + ) + .await + .unwrap_err() + .code(), + Code::FailedPrecondition + ); + assert!( + runtime + .store + .get_sandbox_image_state(original.object_id()) + .await + .unwrap() + .is_none() + ); + assert!( + !driver + .calls() + .iter() + .any(|call| matches!(call, FakeComputeDriverCall::UpdateSandboxImage { .. })) + ); + } +} diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index 1ec4cfd522..d56e1ff27f 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -4,6 +4,7 @@ //! Gateway-owned compute orchestration over a pluggable compute backend. pub mod driver_config; +mod image_update; pub mod lease; mod mutation_guard; pub mod provisioning_deadline; @@ -42,6 +43,7 @@ use openshell_core::proto::compute::v1::{ ResourceRequirements as DriverSandboxResourceRequirements, StartSandboxRequest, StopSandboxRequest, ValidateSandboxCreateRequest, WatchSandboxesEvent, WatchSandboxesRequest, WorkloadIdentityRequest, compute_driver_client::ComputeDriverClient, + compute_driver_image_updates_client::ComputeDriverImageUpdatesClient, compute_driver_server::ComputeDriver, watch_sandboxes_event, }; use openshell_core::proto::{ @@ -80,6 +82,13 @@ pub type DriverWatchStream = pub type SharedComputeDriver = Arc + Send + Sync>; +/// Optional image-update registration, independent of the baseline driver object. +pub type SharedComputeDriverImageUpdates = Arc< + dyn openshell_core::compute::ComputeDriver + + Send + + Sync, +>; + fn supports_isolation_registration(capabilities: &[String]) -> bool { capabilities.iter().any(|capability| { capability == openshell_core::extension_protocol::COMPUTE_ISOLATION_BACKEND @@ -543,6 +552,10 @@ impl AcquiredRemoteDriverEndpoint { #[derive(Debug, Clone)] struct RemoteComputeDriver { client: RemoteComputeDriverClient, + configuration_updates_client: openshell_core::proto::compute::v1::compute_driver_configuration_updates_client::ComputeDriverConfigurationUpdatesClient>, + image_updates_client: ComputeDriverImageUpdatesClient< + tonic::service::interceptor::InterceptedService, + >, admission_acknowledgement: Arc>>, } @@ -553,7 +566,12 @@ type RemoteComputeDriverClient = ComputeDriverClient< impl RemoteComputeDriver { fn new(channel: Channel) -> Self { Self { - client: ComputeDriverClient::with_interceptor(channel, TraceContextInterceptor), + client: ComputeDriverClient::with_interceptor(channel.clone(), TraceContextInterceptor), + configuration_updates_client: openshell_core::proto::compute::v1::compute_driver_configuration_updates_client::ComputeDriverConfigurationUpdatesClient::with_interceptor(channel.clone(), TraceContextInterceptor), + image_updates_client: ComputeDriverImageUpdatesClient::with_interceptor( + channel, + TraceContextInterceptor, + ), admission_acknowledgement: Arc::new(StdMutex::new(None)), } } @@ -594,6 +612,38 @@ impl RemoteComputeDriver { } } +#[tonic::async_trait] +impl openshell_core::compute::ComputeDriver for RemoteComputeDriver { + async fn update_sandbox_configuration( + &self, + request: Request, + ) -> Result< + tonic::Response, + Status, + > { + self.verify_admission_policy(false).await?; + self.configuration_updates_client + .clone() + .update_sandbox_configuration(request) + .await + } + + async fn update_sandbox_image( + &self, + request: Request, + ) -> Result< + tonic::Response, + Status, + > { + // An image-only update does not install an external isolation route. + self.verify_admission_policy(false).await?; + self.image_updates_client + .clone() + .update_sandbox_image(request) + .await + } +} + #[tonic::async_trait] impl ComputeDriver for RemoteComputeDriver { type WatchSandboxesStream = DriverWatchStream; @@ -741,11 +791,13 @@ pub struct ComputeRuntime { admission: openshell_core::resource_admission::DriverAdmissionConfig, admission_acknowledgement: String, driver: TracedDriver, + image_update_driver: Option, driver_info: ComputeDriverInfoSnapshot, telemetry_compute_driver: TelemetryComputeDriver, driver_process: Option>, default_image: String, image_preparation_timeout_seconds: u32, + sandbox_updates_enabled: bool, store: Arc, sandbox_index: SandboxIndex, sandbox_watch_bus: SandboxWatchBus, @@ -854,6 +906,7 @@ impl ComputeRuntime { Ok(Self { isolation_backend: None, driver: TracedDriver::new(driver, driver_name), + image_update_driver: None, admission: openshell_core::resource_admission::DriverAdmissionConfig::default(), admission_acknowledgement: capabilities.resource_admission_policy, driver_info, @@ -861,6 +914,7 @@ impl ComputeRuntime { driver_process, default_image, image_preparation_timeout_seconds: 1800, + sandbox_updates_enabled: false, store, sandbox_index, sandbox_watch_bus, @@ -984,10 +1038,10 @@ impl ComputeRuntime { tracing_log_bus: TracingLogBus, supervisor_sessions: Arc, ) -> Result { - let driver: SharedComputeDriver = Arc::new(RemoteComputeDriver::new(endpoint.channel)); + let driver = Arc::new(RemoteComputeDriver::new(endpoint.channel)); Self::from_driver( endpoint.name, - driver, + driver.clone(), endpoint.driver_process, store, sandbox_index, @@ -996,6 +1050,15 @@ impl ComputeRuntime { supervisor_sessions, ) .await + .map(|runtime| runtime.with_image_update_driver(Some(driver))) + } + + pub(crate) fn with_image_update_driver( + mut self, + driver: Option, + ) -> Self { + self.image_update_driver = driver; + self } #[must_use] @@ -1652,14 +1715,15 @@ impl ComputeRuntime { Some(guard) => guard, None => runtime.lock_sandbox_for_lifecycle(&lifecycle_guard).await, }; - runtime - .compensate_failed_create_inner( - &created, - lifecycle_guard, - sandbox_guard, - original, - ) - .await + // Keep compensation's future bounded as the persisted sandbox + // status grows (including image-update reconciliation state). + Box::pin(runtime.compensate_failed_create_inner( + &created, + lifecycle_guard, + sandbox_guard, + original, + )) + .await } .instrument(request_span), ) @@ -1844,12 +1908,22 @@ impl ComputeRuntime { return Ok(current); } let automatic_restart = is_automatic_restart_transition(¤t); - // Timeout cleanup owns reclamation while the record remains Error. - // Its later start may recreate missing compute; an ordinary stop must - // not replace that recovery state with Stopping or Stopped. - if phase == SandboxPhase::Error && provisioning_deadline::timed_out(¤t) { + // A timed-out attempt owns reclamation until both its driver work + // has settled (checked above) and cleanup has completed. Once those + // durable facts hold, explicit stop can expose a safe Stopped state + // for image repair without stopping/deleting retained compute again. + // Keep the timeout record: Start still needs its recovery provenance. + let reclaimed_timeout = + phase == SandboxPhase::Error && provisioning_deadline::timed_out(¤t); + if reclaimed_timeout + && current + .status + .as_ref() + .and_then(|status| status.provisioning.as_ref()) + .is_none_or(|record| record.cleanup_completed_time.is_none()) + { return Err(Status::failed_precondition( - "sandbox provisioning timed out; retry start after timeout cleanup completes", + "sandbox provisioning timed out; wait for timeout cleanup before stop or start", )); } // Error does not prove compute has stopped. Use the normal driver stop @@ -1871,18 +1945,28 @@ impl ComputeRuntime { break (current.clone(), current); } - match self - .write_lifecycle_phase( - ¤t, - SandboxPhase::Stopping, - "Stopping", - "Sandbox stop requested", + let (next_phase, reason, message) = if reclaimed_timeout { + ( + SandboxPhase::Stopped, + "StoppedAfterProvisioningTimeout", + "Timed-out compute has been reclaimed; sandbox stopped for repair", ) + } else { + (SandboxPhase::Stopping, "Stopping", "Sandbox stop requested") + }; + match self + .write_lifecycle_phase(¤t, next_phase, reason, message) .await { Ok(stopping) => { self.sandbox_index.update_from_sandbox(&stopping); self.sandbox_watch_bus.notify(&sandbox_id); + if reclaimed_timeout { + self.cleanup_stopped_sandbox_sessions(&stopping) + .await + .map_err(Status::internal)?; + return Ok(stopping); + } break (current, stopping); } Err(crate::persistence::PersistenceError::Conflict { .. }) @@ -2017,6 +2101,7 @@ impl ComputeRuntime { self.validate_launch_signer_configured(authority.is_some())?; } provisioning_operation::ensure_operation_settled(&candidate)?; + self.ensure_no_pending_image_update(&candidate).await?; if provisioning_deadline::timed_out(&candidate) && candidate .status @@ -2054,6 +2139,7 @@ impl ComputeRuntime { let mut attempts = 0; let (previous, starting, launch_authentication) = loop { provisioning_operation::ensure_operation_settled(¤t)?; + self.ensure_no_pending_image_update(¤t).await?; let phase = SandboxPhase::try_from(current.phase()).unwrap_or(SandboxPhase::Unknown); if phase == SandboxPhase::Ready { return Ok(current); @@ -5642,9 +5728,15 @@ impl ComputeRuntime { .await .map_err(|e| e.to_string())?; if let Some(sandbox) = sandbox.as_ref() { + // Timeout cleanup can emit Deleted before an explicit stop exposes + // Stopped for repair. A delayed (or repeated) event must retain that + // recovery record too; explicit deletion still transitions to Deleting. if provisioning_deadline::driver_operation_pending(sandbox) || (provisioning_deadline::timed_out(sandbox) - && sandbox.phase() == i32::from(SandboxPhase::Error)) + && matches!( + SandboxPhase::try_from(sandbox.phase()), + Ok(SandboxPhase::Error | SandboxPhase::Stopped) + )) { return Ok(()); } @@ -5690,6 +5782,10 @@ impl ComputeRuntime { } async fn cleanup_sandbox_owned_records(&self, sandbox: &Sandbox) -> Result<(), String> { + self.store + .delete_sandbox_image_state(sandbox.object_id()) + .await + .map_err(|error| format!("delete sandbox image state: {error}"))?; self.cleanup_ssh_identity(sandbox) .await .map_err(|error| error.to_string())?; @@ -7838,6 +7934,7 @@ pub fn new_test_runtime_with_driver( ComputeRuntime { isolation_backend: None, driver: TracedDriver::new(driver, "test".to_string()), + image_update_driver: None, admission: openshell_core::resource_admission::DriverAdmissionConfig { allow_driver_config: true, ..Default::default() @@ -7859,6 +7956,7 @@ pub fn new_test_runtime_with_driver( driver_process: None, default_image: "openshell/sandbox:test".to_string(), image_preparation_timeout_seconds: 1800, + sandbox_updates_enabled: false, store, sandbox_index: SandboxIndex::new(), sandbox_watch_bus: SandboxWatchBus::new(), @@ -8670,6 +8768,9 @@ mod tests { } } + #[tonic::async_trait] + impl openshell_core::compute::ComputeDriver for ControlledDriver {} + #[tonic::async_trait] impl ComputeDriver for ControlledDriver { async fn authenticate_sandbox( @@ -8959,6 +9060,7 @@ mod tests { ComputeRuntime { isolation_backend: None, driver: TracedDriver::new(driver, "test-driver".to_string()), + image_update_driver: None, admission: openshell_core::resource_admission::DriverAdmissionConfig { allow_driver_config: true, ..Default::default() @@ -8980,6 +9082,7 @@ mod tests { driver_process: None, default_image: "openshell/sandbox:test".to_string(), image_preparation_timeout_seconds: 1800, + sandbox_updates_enabled: false, store, sandbox_index: SandboxIndex::new(), sandbox_watch_bus: SandboxWatchBus::new(), @@ -11935,6 +12038,9 @@ mod tests { #[derive(Debug, Default)] struct FailingDriver(TestDriver); + #[tonic::async_trait] + impl openshell_core::compute::ComputeDriver for FailingDriver {} + #[tonic::async_trait] impl ComputeDriver for FailingDriver { async fn authenticate_sandbox( @@ -14493,7 +14599,7 @@ mod tests { tokio::time::timeout( Duration::from_secs(1), - runtime.reconcile_store_with_backend(Duration::ZERO), + Box::pin(runtime.reconcile_store_with_backend(Duration::ZERO)), ) .await .expect("prune sweep blocked on the stuck driver delete call") @@ -16911,10 +17017,11 @@ mod tests { async fn remote_compute_driver_interceptor_propagates_every_rpc() { use crate::otel_tracing::test_exporter; use crate::test_support::FakeComputeDriver; + use openshell_core::compute::ComputeDriver as _; let dir = tempfile::tempdir().unwrap(); let socket_path = dir.path().join("compute-driver.sock"); - let driver = FakeComputeDriver::new(); + let driver = FakeComputeDriver::new().with_sandbox_image_updates(); let _server = driver.serve_uds(&socket_path).unwrap(); let endpoint = connect_remote_compute_driver("external-test", &socket_path) .await @@ -16968,6 +17075,16 @@ mod tests { .watch_sandboxes(Request::new(WatchSandboxesRequest {})) .await .unwrap(); + remote + .update_sandbox_image(Request::new( + openshell_core::proto::compute::v1::UpdateSandboxImageRequest { + sandbox_id: sandbox.id.clone(), + image: "example/workload:v2".into(), + ..Default::default() + }, + )) + .await + .unwrap(); remote .delete_sandbox(Request::new(DeleteSandboxRequest { sandbox_id: sandbox.id, @@ -16985,7 +17102,7 @@ mod tests { let traceparents = driver.traceparents(); assert_eq!( traceparents.len(), - 10, + 12, "the client interceptor should cover every RPC" ); assert!( @@ -17538,12 +17655,160 @@ endpoint = { kind = "unix", socket_path = "/run/agent-substrate/isolation.sock" .await .is_err() ); + let status = openshell_core::compute::ComputeDriver::update_sandbox_image( + &remote, + Request::new(openshell_core::proto::compute::v1::UpdateSandboxImageRequest::default()), + ) + .await + .unwrap_err(); + assert_eq!(status.code(), Code::FailedPrecondition); assert_eq!( driver.calls(), - vec![FakeComputeDriverCall::GetCapabilities; 3] + vec![FakeComputeDriverCall::GetCapabilities; 4] ); } + #[tokio::test] + #[cfg(unix)] + async fn remote_compute_driver_negotiates_optional_image_service() { + use crate::test_support::{FakeComputeDriver, FakeComputeDriverCall}; + use openshell_core::GetResourceVersion as _; + use openshell_core::extension_protocol::COMPUTE_UPDATE_SANDBOX_IMAGE; + + for supported in [false, true] { + let dir = tempfile::tempdir().unwrap(); + let socket_path = dir.path().join("image-updates.sock"); + let driver = FakeComputeDriver::new(); + let driver = if supported { + driver.with_sandbox_image_updates() + } else { + driver + }; + let _server = driver.serve_uds(&socket_path).unwrap(); + let endpoint = connect_remote_compute_driver("external-test", &socket_path) + .await + .unwrap(); + let store = Arc::new(crate::persistence::test_store().await); + crate::ensure_default_workspace(&store).await.unwrap(); + let mut runtime = ComputeRuntime::new_remote_driver( + endpoint, + store.clone(), + SandboxIndex::new(), + SandboxWatchBus::new(), + TracingLogBus::new(), + Arc::new(SupervisorSessionRegistry::new()), + ) + .await + .unwrap(); + runtime.sandbox_updates_enabled = true; + assert!( + !runtime + .driver_info + .negotiated_extension + .supported_capabilities + .iter() + .any(|capability| capability + == openshell_core::extension_protocol::COMPUTE_ISOLATION_BACKEND), + "image-only updates do not require external isolation registration" + ); + assert_eq!( + runtime + .driver_info + .negotiated_extension + .supported_capabilities + .iter() + .any(|capability| capability == COMPUTE_UPDATE_SANDBOX_IMAGE), + supported, + ); + let mut sandbox = sandbox_record("sb-image", "image-sandbox", SandboxPhase::Stopped); + sandbox.metadata.as_mut().unwrap().annotations.insert( + COMPUTE_RUNTIME_IDENTITY_ANNOTATION.into(), + "stable-runtime".into(), + ); + sandbox.spec = Some(SandboxSpec { + template: Some(SandboxTemplate { + image: "example/workload:v1".into(), + ..Default::default() + }), + ..Default::default() + }); + store.put_message(&sandbox).await.unwrap(); + let sandbox = store + .get_message::("sb-image") + .await + .unwrap() + .unwrap(); + driver.clear_calls(); + let result = runtime + .update_sandbox_image( + "sb-image", + "example/workload:v2".into(), + sandbox.get_resource_version(), + ) + .await; + let requests = driver + .calls() + .into_iter() + .filter_map(|call| match call { + FakeComputeDriverCall::UpdateSandboxImage { request } => Some(request), + _ => None, + }) + .collect::>(); + if supported { + let updated = result.unwrap(); + assert_eq!( + updated.spec.unwrap().template.unwrap().image, + "example/workload:v2" + ); + assert_eq!( + requests.len(), + 2, + "preflight and mutation use the optional service" + ); + assert!(requests[0].validate_only); + assert!(!requests[1].validate_only); + for request in &requests { + assert_eq!(request.sandbox_id, "sb-image"); + assert_eq!(request.expected_runtime_identity, "stable-runtime"); + assert_eq!(request.expected_image, "example/workload:v1"); + assert_eq!(request.image, "example/workload:v2"); + } + assert_eq!(requests[0].operation_id, requests[1].operation_id); + } else { + assert_eq!(result.unwrap_err().code(), Code::Unimplemented); + assert!( + requests.is_empty(), + "capability admission must precede extension calls" + ); + let unchanged = store + .get_message::("sb-image") + .await + .unwrap() + .unwrap(); + assert_eq!(unchanged, sandbox); + } + } + } + + #[tokio::test] + async fn in_process_driver_defaults_image_updates_to_unimplemented() { + // NoopTestDriver implements only the baseline generated trait. + let mut runtime = test_runtime(Arc::new(NoopTestDriver::default())) + .await + .with_sandbox_updates(true); + runtime + .driver_info + .negotiated_extension + .supported_capabilities + .push(openshell_core::extension_protocol::COMPUTE_UPDATE_SANDBOX_IMAGE.into()); + let error = runtime + .update_sandbox_image("not-created", "example/workload:v2".into(), 1) + .await + .unwrap_err(); + assert_eq!(error.code(), Code::Unimplemented); + assert!(runtime.image_update_driver.is_none()); + } + #[tokio::test] async fn negotiated_launch_requirement_rejects_create_before_driver_or_store() { let driver = Arc::new(TestDriver { diff --git a/crates/openshell-server/src/compute/tests/error_recovery.rs b/crates/openshell-server/src/compute/tests/error_recovery.rs index c6c2d45aee..7fdfea0eeb 100644 --- a/crates/openshell-server/src/compute/tests/error_recovery.rs +++ b/crates/openshell-server/src/compute/tests/error_recovery.rs @@ -152,6 +152,10 @@ async fn pending_driver_operation_blocks_error_recovery_without_mutation() { let runtime = test_runtime(driver.clone()).await; let mut sandbox = error_sandbox_record("pending-id", "pending", "StartFailed"); let mut operation = provisioning_deadline::new_record(openshell_core::time::now_ms()); + // Even a completion timestamp cannot override an unsettled original driver + // operation. This also fences malformed/stale timeout cleanup evidence. + operation.timeout_time = openshell_core::time::timestamp_from_millis(300_000).ok(); + operation.cleanup_completed_time = openshell_core::time::timestamp_from_millis(320_000).ok(); operation.driver_operation_pending = true; operation.driver_operation_id = "owned-operation".into(); sandbox.status.as_mut().unwrap().provisioning = Some(operation); @@ -188,14 +192,39 @@ async fn timeout_stop_preserves_cleanup_and_direct_start_recovery() { runtime.store.put_message(&sandbox).await.unwrap(); let before = stored(&runtime, sandbox.object_id()).await; - let error = runtime - .stop_sandbox("default", "timeout") - .await - .unwrap_err(); - assert_eq!(error.code(), Code::FailedPrecondition); - assert!(error.message().contains("start")); - assert_eq!(stored(&runtime, sandbox.object_id()).await, before); - assert_eq!(driver.stop_calls(), 0, "timeout cleanup owns reclamation"); + let stop = runtime.stop_sandbox("default", "timeout").await; + if cleanup_complete { + let stopped = stop.expect( + "completed timeout cleanup permits an explicit stopped state for image repair", + ); + assert_eq!(stopped.phase(), SandboxPhase::Stopped as i32); + assert_eq!(stopped.object_id(), before.object_id()); + assert_eq!(stopped.spec, before.spec); + assert_eq!( + stopped.status.as_ref().unwrap().provisioning, + before.status.as_ref().unwrap().provisioning + ); + let repeated = runtime.stop_sandbox("default", "timeout").await.unwrap(); + assert_eq!( + repeated, stopped, + "the retained stopped transition is idempotent" + ); + // Docker cleanup and its snapshot poll can both report deletion of + // the cancelled preparation after explicit stop has returned. + for _ in 0..2 { + runtime.apply_deleted(sandbox.object_id()).await.unwrap(); + assert_eq!(stored(&runtime, sandbox.object_id()).await, stopped); + } + } else { + let error = stop.unwrap_err(); + assert_eq!(error.code(), Code::FailedPrecondition); + assert_eq!(stored(&runtime, sandbox.object_id()).await, before); + } + assert_eq!( + driver.stop_calls(), + 0, + "timeout cleanup already owns reclamation" + ); let result = runtime.start_sandbox("default", "timeout").await; if cleanup_complete { @@ -214,6 +243,35 @@ async fn timeout_stop_preserves_cleanup_and_direct_start_recovery() { } } +#[tokio::test] +async fn reclaimed_timeout_can_still_start_directly_without_stop() { + let driver = ControlledDriver::new(); + driver.set_start_outcome(ControlledLifecycleOutcome::NotFound); + driver.track_compute.store(true, Ordering::SeqCst); + let runtime = test_runtime(driver.clone()).await; + let mut sandbox = error_sandbox_record( + "direct-timeout-id", + "direct-timeout", + "ImagePreparationTimedOut", + ); + let mut operation = provisioning_deadline::new_record(0); + operation.timeout_time = openshell_core::time::timestamp_from_millis(300_000).ok(); + operation.cleanup_completed_time = openshell_core::time::timestamp_from_millis(320_000).ok(); + sandbox.status.as_mut().unwrap().provisioning = Some(operation); + runtime.store.put_message(&sandbox).await.unwrap(); + + let starting = runtime + .start_sandbox("default", "direct-timeout") + .await + .unwrap(); + assert_eq!(starting.phase(), SandboxPhase::Starting as i32); + assert_eq!(starting.object_id(), sandbox.object_id()); + assert!(driver.compute_exists.load(Ordering::SeqCst)); + assert_eq!(driver.start_calls(), 1); + assert_eq!(driver.stop_calls(), 0); + assert_eq!(driver.delete_calls(), 0); +} + #[tokio::test] async fn failed_error_stop_retains_retryable_state_without_claiming_success() { for observed_error in [false, true] { diff --git a/crates/openshell-server/src/compute_factory_compatibility.rs b/crates/openshell-server/src/compute_factory_compatibility.rs new file mode 100644 index 0000000000..3ce003dd97 --- /dev/null +++ b/crates/openshell-server/src/compute_factory_compatibility.rs @@ -0,0 +1,172 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Baseline factory source fixture: no image-update trait or new hook required. + +use std::collections::BTreeMap; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use openshell_core::proto::compute::v1::compute_driver_server::ComputeDriver; + +use crate::test_support::FakeComputeDriver; +use crate::{ + ComputeDriverBuildContext, ComputeDriverFactory, ComputeDriverInstance, + ComputeDriverRegistration, ComputeDriverRegistry, DriverWatchStream, SharedComputeDriver, + SharedComputeDriverImageUpdates, +}; + +// Written using only the baseline generated trait, not the new optional trait. +type BaselineDriver = + Arc + Send + Sync>; + +struct BaselineFactory { + driver: BaselineDriver, + calls: Arc, +} + +#[async_trait::async_trait] +impl ComputeDriverFactory for BaselineFactory { + async fn build( + &self, + _context: ComputeDriverBuildContext<'_>, + ) -> openshell_core::Result { + self.calls.fetch_add(1, Ordering::SeqCst); + // This unchanged baseline expression must compile without a new trait. + Ok(ComputeDriverInstance::InProcess(self.driver.clone())) + } +} + +struct OptInFactory { + driver: Arc, + legacy_build_calls: Arc, + opt_in_calls: Arc, +} + +#[async_trait::async_trait] +impl ComputeDriverFactory for OptInFactory { + async fn build( + &self, + _context: ComputeDriverBuildContext<'_>, + ) -> openshell_core::Result { + self.legacy_build_calls.fetch_add(1, Ordering::SeqCst); + Ok(ComputeDriverInstance::InProcess(self.driver.clone())) + } + + async fn build_with_image_updates( + &self, + _context: ComputeDriverBuildContext<'_>, + ) -> openshell_core::Result<( + ComputeDriverInstance, + Option, + )> { + self.opt_in_calls.fetch_add(1, Ordering::SeqCst); + Ok(( + ComputeDriverInstance::InProcess(self.driver.clone()), + Some(self.driver.clone()), + )) + } +} + +async fn registered_runtime(factory: impl ComputeDriverFactory + 'static) -> crate::ComputeRuntime { + let mut registry = ComputeDriverRegistry::new(); + registry + .install(ComputeDriverRegistration::new("fixture", 0, None, factory).unwrap()) + .unwrap(); + let selection = registry.select(Some("fixture")).unwrap(); + let config = openshell_core::Config::new(None); + let overrides = BTreeMap::new(); + let startup = crate::compute::driver_config::DriverStartupContext { + file: None, + guest_tls: None, + gateway_port: 8080, + gateway_tls_enabled: false, + endpoint_overrides: &overrides, + }; + let store = Arc::new(crate::persistence::test_store().await); + crate::build_compute_runtime( + ®istry, + &selection, + &config, + startup, + store, + crate::sandbox_index::SandboxIndex::new(), + crate::sandbox_watch::SandboxWatchBus::new(), + crate::tracing_bus::TracingLogBus::new(), + Arc::new(crate::supervisor_session::SupervisorSessionRegistry::new()), + tokio::sync::watch::channel(false).1, + ) + .await + .unwrap() + .with_sandbox_updates(true) +} + +#[tokio::test] +async fn baseline_factory_and_trait_object_still_build_without_opt_in() { + // This concrete fixture implements only the baseline generated trait. + let driver: BaselineDriver = Arc::new(crate::compute::NoopTestDriver::default()); + let shared: SharedComputeDriver = driver.clone(); + // Exhaustive baseline enum match also prevents adding a required new variant. + match ComputeDriverInstance::InProcess(shared) { + ComputeDriverInstance::InProcess(actual) => assert!(Arc::ptr_eq(&actual, &driver)), + ComputeDriverInstance::ManagedRemote(_) => panic!("expected in-process driver"), + } + let calls = Arc::new(AtomicUsize::new(0)); + let runtime = registered_runtime(BaselineFactory { + driver, + calls: calls.clone(), + }) + .await; + assert_eq!(calls.load(Ordering::SeqCst), 1); + assert_eq!( + runtime + .update_sandbox_image("not-created", "example/workload:v2".into(), 1) + .await + .unwrap_err() + .code(), + tonic::Code::Unimplemented + ); +} + +#[tokio::test] +async fn capability_alone_does_not_register_an_in_process_image_updater() { + let driver = Arc::new(FakeComputeDriver::new().with_sandbox_image_updates()); + let runtime = registered_runtime(BaselineFactory { + driver: driver.clone(), + calls: Arc::new(AtomicUsize::new(0)), + }) + .await; + driver.clear_calls(); + assert_eq!( + runtime + .update_sandbox_image("not-created", "example/workload:v2".into(), 1) + .await + .unwrap_err() + .code(), + tonic::Code::Unimplemented + ); + assert!(driver.calls().is_empty()); +} + +#[tokio::test] +async fn registered_opt_in_hook_supplies_the_optional_updater() { + let legacy_calls = Arc::new(AtomicUsize::new(0)); + let opt_in_calls = Arc::new(AtomicUsize::new(0)); + let runtime = registered_runtime(OptInFactory { + driver: Arc::new(FakeComputeDriver::new().with_sandbox_image_updates()), + legacy_build_calls: legacy_calls.clone(), + opt_in_calls: opt_in_calls.clone(), + }) + .await; + assert_eq!(legacy_calls.load(Ordering::SeqCst), 0); + assert_eq!(opt_in_calls.load(Ordering::SeqCst), 1); + // Passing capability/registration admission reaches the sandbox lookup. + assert_eq!( + runtime + .update_sandbox_image("not-created", "example/workload:v2".into(), 1) + .await + .unwrap_err() + .code(), + tonic::Code::NotFound + ); +} diff --git a/crates/openshell-server/src/config_file.rs b/crates/openshell-server/src/config_file.rs index 817a6a02ad..36a2563946 100644 --- a/crates/openshell-server/src/config_file.rs +++ b/crates/openshell-server/src/config_file.rs @@ -140,6 +140,9 @@ pub struct GatewayFileSection { /// Absolute preparation budget for new attempts; existing deadlines persist. #[serde(default)] pub image_preparation_timeout_seconds: Option, + /// Explicit activation only after the compatible deployment rollout ends. + #[serde(default)] + pub enable_sandbox_updates: Option, #[serde(default)] pub grpc_rate_limit_requests: Option, #[serde(default)] diff --git a/crates/openshell-server/src/gateway_members.rs b/crates/openshell-server/src/gateway_members.rs index 33d68414df..a61df4b509 100644 --- a/crates/openshell-server/src/gateway_members.rs +++ b/crates/openshell-server/src/gateway_members.rs @@ -43,6 +43,9 @@ fn member_object_id(replica_id: &str) -> String { struct MemberPayload { replica_id: String, peer_endpoint: String, + /// Binary capability, not feature activation. Missing denotes old replicas. + #[serde(default)] + image_update_protocol: u32, } /// A gateway replica that was alive as of its last refresh. @@ -71,6 +74,7 @@ impl GatewayMemberIndex { let payload = MemberPayload { replica_id: replica_id.to_string(), peer_endpoint: peer_endpoint.to_string(), + image_update_protocol: 2, }; let bytes = serde_json::to_vec(&payload) .map_err(|err| PersistenceError::Encode(err.to_string()))?; diff --git a/crates/openshell-server/src/grpc/mod.rs b/crates/openshell-server/src/grpc/mod.rs index 0368025348..c572c9b209 100644 --- a/crates/openshell-server/src/grpc/mod.rs +++ b/crates/openshell-server/src/grpc/mod.rs @@ -242,6 +242,16 @@ impl OpenShellService { // Trait impl — thin delegation to submodules // --------------------------------------------------------------------------- +#[tonic::async_trait] +impl openshell_core::proto::sandbox_updates_server::SandboxUpdates for OpenShellService { + async fn update_sandbox( + &self, + request: Request, + ) -> Result, Status> { + mutation_replay::run(&self.state, request).await + } +} + #[tonic::async_trait] impl OpenShell for OpenShellService { async fn health( diff --git a/crates/openshell-server/src/grpc/mutation_replay.rs b/crates/openshell-server/src/grpc/mutation_replay.rs index 1a39b3dea0..837d26466c 100644 --- a/crates/openshell-server/src/grpc/mutation_replay.rs +++ b/crates/openshell-server/src/grpc/mutation_replay.rs @@ -102,6 +102,9 @@ struct AuthorizationBarrier { pub(super) trait Mutation: Message + Default + Send + Sync + 'static { type Output: Send + 'static; const METHOD: &'static str; + /// Preserve legacy replay keys while allowing independent services to use + /// fully qualified identities for newly introduced methods. + const REPLAY_METHOD: &'static str = Self::METHOD; const PROTECTED: bool = false; // Streaming producers, not returned stream handles, confirm completion. const DEFERRED: bool = false; @@ -217,7 +220,7 @@ pub(super) async fn run( let bucket = format!("_mutation/{caller}"); let key = hash_json(&serde_json::json!([ caller, - M::METHOD, + M::REPLAY_METHOD, scope.name, request_id ]))?; diff --git a/crates/openshell-server/src/grpc/mutation_replay/ordinary.rs b/crates/openshell-server/src/grpc/mutation_replay/ordinary.rs index 25410043f6..a88c6abb9d 100644 --- a/crates/openshell-server/src/grpc/mutation_replay/ordinary.rs +++ b/crates/openshell-server/src/grpc/mutation_replay/ordinary.rs @@ -335,10 +335,11 @@ async fn profile_scope( } macro_rules! mutation { - ($req:ty, $resp:ty, $method:literal, $handler:path, $auth:expr, $capture:expr, $restore:expr) => { + ($req:ty, $resp:ty, $method:literal, $handler:path, $auth:expr, $capture:expr, $restore:expr $(, $replay_method:literal)?) => { impl Mutation for $req { type Output = $resp; const METHOD: &'static str = $method; + $(const REPLAY_METHOD: &'static str = $replay_method;)? const PROTECTED: bool = true; fn request_id(&self) -> &str { &self.request_id @@ -389,7 +390,7 @@ macro_rules! scoped_mutation { } macro_rules! sandbox_scoped_mutation { - ($req:ty, $resp:ty, $method:literal, $handler:path, $role:ident, $capture:expr, $restore:expr) => { + ($req:ty, $resp:ty, $method:literal, $handler:path, $role:ident, $capture:expr, $restore:expr $(, $replay_method:literal)?) => { mutation!( $req, $resp, @@ -406,6 +407,7 @@ macro_rules! sandbox_scoped_mutation { }, $capture, $restore + $(, $replay_method)? ); }; } @@ -423,7 +425,7 @@ fn sandbox_receipt( } macro_rules! sandbox_mutation { - ($req:ty, $method:literal, $handler:path) => { + ($req:ty, $method:literal, $handler:path $(, $replay_method:literal)?) => { sandbox_scoped_mutation!( $req, SandboxResponse, @@ -444,6 +446,7 @@ macro_rules! sandbox_mutation { service_urls: HashMap::new(), }) } + $(, $replay_method)? ); }; } @@ -471,6 +474,12 @@ scoped_mutation!( }) } ); +sandbox_mutation!( + openshell_core::proto::UpdateSandboxRequest, + "UpdateSandbox", + sandbox::handle_update_sandbox, + "/openshell.v1.SandboxUpdates/UpdateSandbox" +); sandbox_mutation!( StartSandboxRequest, "StartSandbox", diff --git a/crates/openshell-server/src/grpc/sandbox.rs b/crates/openshell-server/src/grpc/sandbox.rs index cac481b149..41f67774a1 100644 --- a/crates/openshell-server/src/grpc/sandbox.rs +++ b/crates/openshell-server/src/grpc/sandbox.rs @@ -315,6 +315,112 @@ pub(super) async fn handle_create_sandbox( result } +/// Only the workload image is mutable in the first update contract. Runtime +/// replacement is an explicit Stop/Update/Start workflow, never Delete/Create. +pub(super) async fn handle_update_sandbox( + state: &Arc, + request: Request, +) -> Result, Status> { + let principal = super::extract_principal(&request)?; + let request = request.into_inner(); + validate_image_update_request(&request)?; + let sandbox = resolve_and_authorize_sandbox_name( + state, + &principal, + &request.name, + crate::auth::workspace_authz::selected_workspace_name(request.workspace_scope.as_ref())?, + MinWorkspaceRole::User, + ) + .await?; + super::workspace::resolve_workspace(state.store.as_ref(), sandbox.object_workspace()) + .await? + .ensure_active()?; + let mut candidate = sandbox.clone(); + let spec = candidate + .spec + .as_mut() + .ok_or_else(|| Status::failed_precondition("sandbox has no spec"))?; + if let Some(image) = &request.image { + spec.template + .get_or_insert_with(Default::default) + .image + .clone_from(image); + } + openshell_core::sandbox_update::apply_configuration( + &mut candidate, + request.command.as_ref(), + request.environment.as_ref(), + request.labels.as_ref(), + request.annotations.as_ref(), + ); + let candidate_spec = candidate.spec.as_ref().expect("spec checked above"); + validate_sandbox_spec(&request.name, candidate_spec)?; + + let sandbox = Box::pin( + state + .compute + .update_sandbox_configuration(sandbox.object_id(), &request), + ) + .await?; + Ok(Response::new(SandboxResponse { + sandbox: Some(sandbox), + service_urls: HashMap::new(), + })) +} + +fn validate_image_update_request( + request: &openshell_core::proto::UpdateSandboxRequest, +) -> Result<(), Status> { + if request.name.is_empty() { + return Err(Status::invalid_argument("name is required")); + } + if request.expected_resource_version == 0 { + return Err(Status::invalid_argument( + "expected_resource_version must be nonzero", + )); + } + if request.image.is_none() + && request.command.is_none() + && request.environment.is_none() + && request.labels.is_none() + && request.annotations.is_none() + { + return Err(Status::invalid_argument( + "at least one update field is required", + )); + } + if let Some(image) = &request.image + && (image.is_empty() + || image.len() > super::MAX_TEMPLATE_STRING_LEN + || image.chars().any(|c| c.is_whitespace() || c.is_control())) + { + return Err(Status::invalid_argument( + "image must be a nonempty bounded OCI reference without whitespace or control characters", + )); + } + for map in [request.labels.as_ref(), request.annotations.as_ref()] + .into_iter() + .flatten() + { + super::validation::validate_annotations(&map.values, "metadata")?; + if map + .values + .keys() + .any(|key| openshell_core::sandbox_update::is_system_metadata_key(key)) + { + return Err(Status::invalid_argument( + "system metadata cannot be updated", + )); + } + } + if let Some(labels) = &request.labels { + for value in labels.values.values() { + super::validation::validate_label_value(value)?; + } + } + Ok(()) +} + /// Allocate a gateway-owned staging slot for a local rootfs tar archive. /// /// The caller writes the archive to the returned path and then names the token @@ -10366,3 +10472,6 @@ mod tests { )); } } + +#[cfg(test)] +mod image_update_tests; diff --git a/crates/openshell-server/src/grpc/sandbox/image_update_tests.rs b/crates/openshell-server/src/grpc/sandbox/image_update_tests.rs new file mode 100644 index 0000000000..d70d1ab47b --- /dev/null +++ b/crates/openshell-server/src/grpc/sandbox/image_update_tests.rs @@ -0,0 +1,427 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +use super::*; +use crate::grpc::test_support::{authed_request, test_server_state}; +use crate::test_support::{FakeComputeDriver, FakeComputeDriverCall}; +use openshell_core::proto::compute::v1::GetCapabilitiesResponse; +use openshell_core::proto::datamodel::v1::ObjectMeta; +use openshell_core::proto::sandbox_updates_client::SandboxUpdatesClient; +use openshell_core::proto::{SandboxCommandUpdate, UpdateSandboxRequest}; +use tonic::Code; + +struct RunningService(tokio::task::JoinHandle<()>); + +impl Drop for RunningService { + fn drop(&mut self) { + self.0.abort(); + } +} + +async fn wire_client( + state: Arc, +) -> ( + SandboxUpdatesClient, + RunningService, +) { + use crate::multiplex::MultiplexService; + use openshell_core::proto::open_shell_client::OpenShellClient; + + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let address = listener.local_addr().unwrap(); + // Use the production gateway multiplexer, not a standalone tonic server: + // this proves registration, routing and principal injection on the same + // listener as the unchanged OpenShell service. + let server = RunningService(tokio::spawn(async move { + let mut connections = tokio::task::JoinSet::new(); + loop { + let (stream, peer) = listener.accept().await.unwrap(); + let service = MultiplexService::new(state.clone()); + connections.spawn(async move { + service + .serve(stream, crate::auth::audit::ConnectionSource::Peer(peer)) + .await + .unwrap(); + }); + } + })); + let channel = tonic::transport::Endpoint::from_shared(format!("http://{address}")) + .unwrap() + .connect() + .await + .unwrap(); + OpenShellClient::new(channel.clone()) + .health(openshell_core::proto::HealthRequest {}) + .await + .unwrap(); + (SandboxUpdatesClient::new(channel), server) +} + +#[test] +fn image_updates_have_their_own_authenticated_workspace_scoped_service() { + use crate::auth::method_authz::{AuthMode, lookup}; + use crate::grpc::mutation_replay::Mutation; + + let path = "/openshell.v1.SandboxUpdates/UpdateSandbox"; + let authorization = lookup(path).expect("new service must participate in gateway auth"); + assert_eq!(authorization.auth_mode, AuthMode::Bearer); + assert_eq!(authorization.scope.as_deref(), Some("sandbox:write")); + assert_eq!(authorization.workspace_role.as_deref(), Some("user")); + assert!(lookup("/openshell.v1.OpenShell/UpdateSandbox").is_none()); + assert_eq!(::REPLAY_METHOD, path); +} + +fn request() -> UpdateSandboxRequest { + UpdateSandboxRequest { + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + name: "image-update".into(), + image: Some("example/workload:new".into()), + expected_resource_version: 1, + request_id: String::new(), + ..Default::default() + } +} + +#[test] +fn image_only_request_requires_target_image_and_conditional_version() { + let valid = request(); + validate_image_update_request(&valid).unwrap(); + for image in ["", " ", "example/workload:tag\n", "bad image", "a\0b"] { + assert_eq!( + validate_image_update_request(&UpdateSandboxRequest { + image: Some(image.into()), + ..valid.clone() + }) + .unwrap_err() + .code(), + Code::InvalidArgument + ); + } + assert!( + validate_image_update_request(&UpdateSandboxRequest { + image: Some("a".repeat(super::super::MAX_TEMPLATE_STRING_LEN + 1)), + ..valid.clone() + }) + .is_err() + ); + assert!( + validate_image_update_request(&UpdateSandboxRequest { + name: String::new(), + ..valid.clone() + }) + .is_err() + ); + assert!( + validate_image_update_request(&UpdateSandboxRequest { + expected_resource_version: 0, + ..valid + }) + .is_err() + ); +} + +async fn state() -> (Arc, Arc, Sandbox) { + let mut state = test_server_state().await; + Arc::get_mut(&mut state) + .unwrap() + .config + .enable_sandbox_updates = true; + let driver = Arc::new( + FakeComputeDriver::new().with_capabilities(GetCapabilitiesResponse { + driver_name: "image-test".into(), + default_image: "example/workload:old".into(), + extension: Some(openshell_core::extension_protocol::extension_metadata( + openshell_core::extension_protocol::ExtensionFamily::Compute, + "openshell/image-test", + "test", + [ + openshell_core::extension_protocol::COMPUTE_UPDATE_SANDBOX_IMAGE.into(), + openshell_core::extension_protocol::COMPUTE_UPDATE_SANDBOX_CONFIGURATION.into(), + ], + )), + ..Default::default() + }), + ); + let compute = crate::compute::ComputeRuntime::from_driver( + "image-test".into(), + driver.clone(), + None, + state.store.clone(), + crate::sandbox_index::SandboxIndex::new(), + crate::sandbox_watch::SandboxWatchBus::new(), + crate::tracing_bus::TracingLogBus::new(), + Arc::new(crate::supervisor_session::SupervisorSessionRegistry::new()), + ) + .await + .unwrap() + .with_image_update_driver(Some(driver.clone())) + .with_sandbox_updates(true); + Arc::get_mut(&mut state).unwrap().compute = compute; + let mut sandbox = Sandbox { + metadata: Some(ObjectMeta { + id: "stable-sandbox-id".into(), + name: "image-update".into(), + workspace: "default".into(), + ..Default::default() + }), + spec: Some(SandboxSpec { + template: Some(SandboxTemplate { + image: "example/workload:old".into(), + ..Default::default() + }), + ..Default::default() + }), + ..Default::default() + }; + sandbox.set_phase(SandboxPhase::Stopped as i32); + state.store.put_message(&sandbox).await.unwrap(); + let sandbox = state + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + (state, driver, sandbox) +} + +#[tokio::test] +async fn public_image_update_checks_workspace_and_missing_resource() { + let (state, driver, sandbox) = state().await; + let mut req = request(); + req.expected_resource_version = sandbox.get_resource_version(); + req.workspace_scope = None; + assert_eq!( + handle_update_sandbox(&state, authed_request(req.clone())) + .await + .unwrap_err() + .code(), + Code::InvalidArgument + ); + req.workspace_scope = Some(openshell_core::proto::workspace_selector("default")); + req.name = "missing-sandbox".into(); + assert_eq!( + handle_update_sandbox(&state, authed_request(req)) + .await + .unwrap_err() + .code(), + Code::NotFound + ); + assert!( + !driver + .calls() + .iter() + .any(|call| matches!(call, FakeComputeDriverCall::UpdateSandboxImage { .. })) + ); +} + +#[tokio::test] +async fn public_image_update_replays_without_reapplying_or_touching_same_name_replacement() { + let (mut state, driver, sandbox) = state().await; + let directory = tempfile::tempdir().unwrap(); + let key = directory.path().join("private-key"); + std::fs::write(&key, b"test-only stable private fingerprint material").unwrap(); + Arc::get_mut(&mut state).unwrap().config.gateway_jwt = + Some(openshell_core::config::GatewayJwtConfig { + signing_key_path: key, + public_key_path: directory.path().join("public"), + kid_path: directory.path().join("kid"), + gateway_id: "test".into(), + ttl_secs: None, + }); + let (mut client, _server) = wire_client(state.clone()).await; + let req = UpdateSandboxRequest { + expected_resource_version: sandbox.get_resource_version(), + request_id: uuid::Uuid::new_v4().to_string(), + command: Some(SandboxCommandUpdate { + argv: vec!["/usr/local/bin/start-agent".into(), "--updated".into()], + }), + environment: Some(openshell_core::proto::SandboxMapUpdate { + values: HashMap::from([("AGENT_MODE".into(), "updated".into())]), + }), + labels: Some(openshell_core::proto::SandboxMapUpdate { + values: HashMap::from([("team".into(), "agents".into())]), + }), + annotations: Some(openshell_core::proto::SandboxMapUpdate { + values: HashMap::from([("example.com/revision".into(), "2".into())]), + }), + ..request() + }; + let updated = client + .update_sandbox(req.clone()) + .await + .unwrap() + .into_inner() + .sandbox + .unwrap(); + assert_eq!(updated.object_id(), sandbox.object_id()); + assert_eq!( + updated + .spec + .as_ref() + .unwrap() + .template + .as_ref() + .unwrap() + .image, + req.image.as_ref().unwrap().as_str() + ); + assert_eq!( + updated.spec.as_ref().unwrap().command, + req.command.as_ref().unwrap().argv + ); + assert_eq!( + updated.spec.as_ref().unwrap().environment, + req.environment.as_ref().unwrap().values + ); + assert_eq!( + updated.metadata.as_ref().unwrap().labels, + req.labels.as_ref().unwrap().values + ); + let replay = client.update_sandbox(req.clone()).await.unwrap(); + assert_eq!(replay.metadata().get("openshell-replayed").unwrap(), "true"); + assert_eq!(replay.get_ref().sandbox.as_ref().unwrap(), &updated); + assert_eq!(driver.calls().iter().filter(|call| matches!(call, FakeComputeDriverCall::UpdateSandboxConfiguration { request } if !request.image_update.as_ref().unwrap().validate_only)).count(), 1); + let changed = UpdateSandboxRequest { + image: Some("example/workload:other".into()), + ..req.clone() + }; + assert_eq!( + client.update_sandbox(changed).await.unwrap_err().code(), + Code::FailedPrecondition + ); + state + .store + .delete(Sandbox::object_type(), sandbox.object_id()) + .await + .unwrap(); + let mut replacement = sandbox.clone(); + let metadata = replacement.metadata.as_mut().unwrap(); + metadata.id = "replacement-sandbox-id".into(); + metadata.resource_version = 0; + state.store.put_message(&replacement).await.unwrap(); + assert_eq!( + client.update_sandbox(req).await.unwrap_err().code(), + Code::FailedPrecondition + ); + let replacement = state + .store + .get_message::(replacement.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!(replacement.spec, sandbox.spec); + assert_eq!(driver.calls().iter().filter(|call| matches!(call, FakeComputeDriverCall::UpdateSandboxConfiguration { request } if !request.image_update.as_ref().unwrap().validate_only)).count(), 1); +} + +#[test] +fn configuration_update_presence_and_reserved_metadata() { + let mut update = request(); + update.image = None; + assert!(validate_image_update_request(&update).is_err()); + update.command = Some(SandboxCommandUpdate::default()); + validate_image_update_request(&update).unwrap(); + for key in [ + "internal.openshell.ai/auth-epoch", + "openshell.ai/identity", + "agents.x-k8s.io/name", + ] { + update.annotations = Some(openshell_core::proto::SandboxMapUpdate { + values: HashMap::from([(key.into(), "forged".into())]), + }); + assert!(validate_image_update_request(&update).is_err()); + update.labels = update.annotations.take(); + assert!(validate_image_update_request(&update).is_err()); + } +} + +#[tokio::test] +async fn pending_update_retry_preserves_later_update_config_annotations() { + use openshell_core::proto::{SandboxMapUpdate, UpdateConfigRequest}; + + let (state, driver, sandbox) = state().await; + let mut update = UpdateSandboxRequest { + expected_resource_version: sandbox.get_resource_version(), + annotations: Some(SandboxMapUpdate { + values: HashMap::from([ + ("example.com/revision".into(), "original".into()), + ("example.com/retained".into(), "value".into()), + ]), + }), + ..request() + }; + driver.set_image_update_error(Some(Code::Unavailable)); + assert_eq!( + handle_update_sandbox(&state, authed_request(update.clone())) + .await + .unwrap_err() + .code(), + Code::Unavailable + ); + let pending = state + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + + // Exercise the existing public annotation writer while the update is + // pending, including both a new key and an overwrite of a selected key. + let annotations = crate::grpc::policy::handle_update_config( + &state, + authed_request(UpdateConfigRequest { + sandbox: sandbox.object_name().into(), + workspace_scope: update.workspace_scope.clone(), + expected_resource_version: pending.get_resource_version(), + setting_key: "log_level".into(), + delete_setting: true, + annotations: HashMap::from([ + ("example.com/revision".into(), "later".into()), + ("example.com/added".into(), "new".into()), + ]), + ..Default::default() + }), + ) + .await + .unwrap() + .into_inner() + .annotations; + assert_eq!(annotations.get("example.com/retained").unwrap(), "value"); + let current = state + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + update.expected_resource_version = current.get_resource_version(); + driver.set_image_update_error(None); + let completed = handle_update_sandbox(&state, authed_request(update)) + .await + .unwrap() + .into_inner() + .sandbox + .unwrap(); + assert_eq!( + completed.metadata.as_ref().unwrap().annotations, + annotations + ); + assert_eq!(completed.phase(), SandboxPhase::Stopped as i32); + assert!( + completed + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .is_none_or(|record| !record.driver_operation_pending) + ); + assert!( + state + .store + .get_sandbox_image_state(sandbox.object_id()) + .await + .unwrap() + .unwrap() + .pending + .is_none() + ); +} diff --git a/crates/openshell-server/src/lib.rs b/crates/openshell-server/src/lib.rs index f1f991e4ae..73b7bcbab9 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -476,6 +476,7 @@ impl ServerState { credentials: credentials::CredentialRuntime, ) -> Self { let replica_id = compute::lease::replica_id(); + let compute = compute.with_sandbox_updates(config.enable_sandbox_updates); compute.configure_ssh_identities(credentials.clone()); let peer_endpoint = derive_peer_endpoint(&config); let grpc_rate_limiter = multiplex::GrpcRateLimiter::from_config(&config); @@ -1360,6 +1361,7 @@ async fn terminate_signal() { pub use compute::{ AcquiredRemoteDriverEndpoint, DriverWatchStream, ManagedDriverProcess, SharedComputeDriver, + SharedComputeDriverImageUpdates, }; /// Driver instance returned by a compiled compute-driver factory. @@ -1419,6 +1421,23 @@ pub trait ComputeDriverFactory: Send + Sync { } async fn build(&self, context: ComputeDriverBuildContext<'_>) -> Result; + + /// Build with a separately registered, optional in-process image updater. + /// + /// Existing factories only implement `build`; their baseline driver object + /// remains sufficient and this hook defaults to no image-update support. + /// Supporting factories return the same driver instance in both slots and + /// advertise the negotiated image-update capability. Remote drivers expose + /// their extension over the existing endpoint instead of using this hook. + async fn build_with_image_updates( + &self, + context: ComputeDriverBuildContext<'_>, + ) -> Result<( + ComputeDriverInstance, + Option, + )> { + self.build(context).await.map(|instance| (instance, None)) + } } /// One named compiled-driver registration. @@ -1840,7 +1859,10 @@ async fn build_compute_runtime( shutdown_rx, shared_supervision: &shared_supervision, }; - let instance = registration.factory.build(build_context).await?; + let (instance, image_updates) = registration + .factory + .build_with_image_updates(build_context) + .await?; let supervisor_host = shared_supervision .into_inner() .map(|supervision| supervision.controller().clone()); @@ -1859,7 +1881,8 @@ async fn build_compute_runtime( .map_err(|error| { Error::execution(format!("failed to create compute runtime: {error}")) })? - .with_supervisor_host(supervisor_host), + .with_supervisor_host(supervisor_host) + .with_image_update_driver(image_updates), ComputeDriverInstance::ManagedRemote(_) if supervisor_host.is_some() => { return Err(Error::config( "only an in-process compute driver can use the gateway's shared supervision", @@ -2071,6 +2094,9 @@ pub(crate) async fn ensure_default_workspace(store: &Store) -> Result<()> { } } +#[cfg(test)] +mod compute_factory_compatibility; + #[cfg(test)] mod tests { use super::{ @@ -2491,7 +2517,7 @@ mod tests { .iter() .map(|service| service.name.as_str()) .collect::>(), - ["openshell.v1.OpenShell"] + ["openshell.v1.OpenShell", "openshell.v1.SandboxUpdates"] ); stop_listener(shutdown, handle).await; diff --git a/crates/openshell-server/src/multiplex.rs b/crates/openshell-server/src/multiplex.rs index baf9b53ef3..413d9e60c2 100644 --- a/crates/openshell-server/src/multiplex.rs +++ b/crates/openshell-server/src/multiplex.rs @@ -18,6 +18,7 @@ use hyper_util::{ }; use metrics::{counter, histogram}; use openshell_core::proto::open_shell_server::OpenShellServer; +use openshell_core::proto::sandbox_updates_server::SandboxUpdatesServer; use openshell_core::proto::supervisor_host::v1::supervisor_host_server::SupervisorHostServer; use openshell_core::{ Config, @@ -305,6 +306,13 @@ impl MultiplexService { openshell, ); let reflection = self.state.reflection_service.clone(); + let sandbox_updates = SandboxUpdatesServer::new(OpenShellService::new(self.state.clone())) + .max_decoding_message_size(MAX_GRPC_DECODE_SIZE); + let sandbox_updates = GatewayInterceptorGrpcService::new( + sandbox_updates, + self.state.gateway_interceptors.clone(), + Some(self.state.clone()), + ); let authz_policy = self.state.config.oidc.as_ref().map(|oidc| AuthzPolicy { admin_role: oidc.admin_role.clone(), user_role: oidc.user_role.clone(), @@ -312,7 +320,7 @@ impl MultiplexService { }); let authenticator_chain = build_authenticator_chain(&self.state); let grpc_service = AuthGrpcRouter::with_peer_identity( - GrpcRouter::new(openshell, reflection), + GrpcRouter::new(openshell, reflection).with_sandbox_updates(sandbox_updates), authenticator_chain, authz_policy, self.state @@ -987,11 +995,12 @@ where } } -/// Combined gRPC service that routes between `OpenShell` and reflection. +/// Combined gRPC service for public gateway services and reflection. #[derive(Clone)] -pub struct GrpcRouter { +pub struct GrpcRouter { openshell: N, reflection: R, + sandbox_updates: Option, } impl GrpcRouter { @@ -999,13 +1008,22 @@ impl GrpcRouter { Self { openshell, reflection, + sandbox_updates: None, + } + } + + fn with_sandbox_updates(self, sandbox_updates: U) -> GrpcRouter { + GrpcRouter { + openshell: self.openshell, + reflection: self.reflection, + sandbox_updates: Some(sandbox_updates), } } } pub const REFLECTION_PATH_PREFIX: &str = "/grpc.reflection.v1."; -impl tower::Service> for GrpcRouter +impl tower::Service> for GrpcRouter where N: tower::Service> + Clone + Send + 'static, N::Response: Send, @@ -1016,6 +1034,11 @@ where + Send + 'static, R::Future: Send, + U: tower::Service, Response = N::Response, Error = N::Error> + + Clone + + Send + + 'static, + U::Future: Send, B: Send + 'static, { type Response = N::Response; @@ -1030,6 +1053,13 @@ where if req.uri().path().starts_with(REFLECTION_PATH_PREFIX) { let mut svc = self.reflection.clone(); Box::pin(async move { svc.ready().await?.call(req).await }) + } else if req + .uri() + .path() + .starts_with("/openshell.v1.SandboxUpdates/") + && let Some(mut svc) = self.sandbox_updates.clone() + { + Box::pin(async move { svc.ready().await?.call(req).await }) } else { let mut svc = self.openshell.clone(); Box::pin(async move { svc.ready().await?.call(req).await }) @@ -2832,6 +2862,13 @@ mod tests { otel_span_name(&http::Method::POST, "/openshell.v1.OpenShell/CreateSandbox"), "openshell.v1.OpenShell/CreateSandbox" ); + assert_eq!( + otel_span_name( + &http::Method::POST, + "/openshell.v1.SandboxUpdates/UpdateSandbox" + ), + "openshell.v1.SandboxUpdates/UpdateSandbox" + ); } /// Non-RPC paths use a low-cardinality method-only name because sandbox @@ -2901,10 +2938,12 @@ mod tests { name, calls: calls.clone(), }; - let mut router = GrpcRouter::new(service("openshell"), service("reflection")); + let mut router = GrpcRouter::new(service("openshell"), service("reflection")) + .with_sandbox_updates(service("sandbox_updates")); for path in [ "/openshell.v1.OpenShell/Health", + "/openshell.v1.SandboxUpdates/UpdateSandbox", "/grpc.reflection.v1.ServerReflection/ServerReflectionInfo", ] { router @@ -2918,7 +2957,10 @@ mod tests { .unwrap(); } - assert_eq!(*calls.lock().unwrap(), vec!["openshell", "reflection"]); + assert_eq!( + *calls.lock().unwrap(), + vec!["openshell", "sandbox_updates", "reflection"] + ); } #[tokio::test] @@ -3014,7 +3056,10 @@ mod tests { .collect(); names.sort(); - assert_eq!(names, vec!["openshell.v1.OpenShell"]); + assert_eq!( + names, + vec!["openshell.v1.OpenShell", "openshell.v1.SandboxUpdates"] + ); let descriptor_response = responses.message().await.unwrap().unwrap(); let Some(MessageResponse::FileDescriptorResponse(response)) = @@ -3034,6 +3079,11 @@ mod tests { .is_some(), "a fresh descriptor pool must resolve the advertised service" ); + assert!( + reflected_pool + .get_service_by_name("openshell.v1.SandboxUpdates") + .is_some() + ); assert!( reflected_pool .get_message_by_name("openshell.v1.HealthRequest") diff --git a/crates/openshell-server/src/persistence/image_activation.rs b/crates/openshell-server/src/persistence/image_activation.rs new file mode 100644 index 0000000000..22acb430a6 --- /dev/null +++ b/crates/openshell-server/src/persistence/image_activation.rs @@ -0,0 +1,315 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Additional, fail-closed deployment checks for operator-activated image updates. +//! +//! These snapshots are not a durable fence or proof that old processes have +//! stopped. Membership can expire, gateways need not advertise peer endpoints, +//! and a backend can connect after the check. The explicit operator activation +//! must attest that incompatible gateways have been drained and retired. The +//! old-understood pending-operation interlock, not these observations, protects +//! an accepted image mutation from a subsequent legacy Start. + +use super::{ObjectRecord, PersistenceError, PersistenceResult, Store, map_db_error}; +use crate::gateway_members::MEMBER_TTL; +use openshell_core::time::now_ms; + +/// Compatibility marker advertised by both gateway `PostgreSQL` connection pools. +/// +/// This marker describes a protocol, not an exact build version. It is neither +/// authentication nor a security boundary: database clients can set it too. +pub(super) const IMAGE_UPDATE_APPLICATION_NAME: &str = "openshell-image-update-v2"; + +const MEMBER_OBJECT_TYPE: &str = "gateway_member"; +const MEMBER_LIMIT: u32 = 1024; + +impl Store { + /// Reject observable incompatible or unknown gateway deployment participants. + /// + /// The caller separately enforces the default-off operator activation flag. + /// `SQLite` returns success under its supported single-gateway deployment + /// contract; this does not enforce process exclusion for a shared file. + /// `PostgreSQL` checks every client backend in this database, not merely + /// registered peers, and then strictly checks all fresh membership records. + /// Administrative clients without the marker also prevent activation. Any + /// database read failure propagates rather than granting compatibility. + pub async fn ensure_image_update_deployment_compatible(&self) -> PersistenceResult<()> { + let Self::Postgres(store) = self else { + return Ok(()); + }; + + let incompatible_backend: bool = sqlx::query_scalar( + r" +SELECT EXISTS ( + SELECT 1 FROM pg_stat_activity + WHERE datname = current_database() + AND (backend_type = 'client backend' OR backend_type IS NULL) + AND application_name IS DISTINCT FROM $1 +) +", + ) + .bind(IMAGE_UPDATE_APPLICATION_NAME) + .fetch_one(&store.pool) + .await + .map_err(|error| map_db_error(&error))?; + if incompatible_backend { + return Err(PersistenceError::Config( + "sandbox image updates require a uniformly compatible gateway deployment; \ + a PostgreSQL client backend has an unknown or legacy image-update protocol" + .into(), + )); + } + + // One extra row distinguishes a complete bounded snapshot from a + // truncated listing. Never silently treat uninspected rows as compatible. + let records = self + .list_by_type(MEMBER_OBJECT_TYPE, MEMBER_LIMIT + 1, 0) + .await?; + ensure_live_members_compatible(&records, now_ms()) + } +} + +fn ensure_live_members_compatible(records: &[ObjectRecord], now: i64) -> PersistenceResult<()> { + if records.len() > MEMBER_LIMIT as usize { + return Err(PersistenceError::Config( + "cannot establish image-update deployment compatibility: gateway membership listing exceeds its limit" + .into(), + )); + } + let ttl_ms = i64::try_from(MEMBER_TTL.as_millis()).unwrap_or(i64::MAX); + for record in records { + // Match membership liveness semantics, including clock-skew clamping. + // Ignoring expired records is not evidence that their processes stopped. + let age_ms = now.saturating_sub(record.updated_at_ms).max(0); + if age_ms >= ttl_ms { + continue; + } + let member: serde_json::Map = + serde_json::from_slice(&record.payload).map_err(|_| { + PersistenceError::Config( + "cannot establish image-update deployment compatibility: a live gateway membership record is not a valid JSON object" + .into(), + ) + })?; + let replica_id = member.get("replica_id").and_then(serde_json::Value::as_str); + let protocol = member + .get("image_update_protocol") + .and_then(serde_json::Value::as_u64); + if replica_id.is_none_or(|id| id.trim().is_empty()) || protocol != Some(2) { + return Err(PersistenceError::Config( + "sandbox image updates require every live gateway membership record to advertise image-update protocol 2 and a nonempty replica id" + .into(), + )); + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn record(payload: &[u8], updated_at_ms: i64) -> ObjectRecord { + ObjectRecord { + object_type: MEMBER_OBJECT_TYPE.into(), + id: "gateway-member:gw-0".into(), + name: "gw-0".into(), + workspace: String::new(), + payload: payload.to_vec(), + created_at_ms: updated_at_ms, + updated_at_ms, + labels: None, + resource_version: 1, + } + } + + #[tokio::test] + async fn sqlite_uses_the_supported_single_gateway_contract() { + let store = super::super::test_store().await; + store + .ensure_image_update_deployment_compatible() + .await + .unwrap(); + } + + #[tokio::test] + #[ignore = "requires a disposable PostgreSQL database and CREATEDB privileges in OPENSHELL_TEST_POSTGRES_URL; run mise run test:rust:postgres"] + async fn postgres_image_activation_checks_client_backends_and_live_members() { + use super::super::test_postgres::TEST_POSTGRES_URL_ENV; + use super::super::{MutationLockSet, WriteCondition}; + use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; + use std::time::Duration; + + // This check intentionally includes every backend in a database. A + // disposable schema would share unmarked test-control/other-test + // sessions and make its positive assertions dependent on test order. + // Keep the unmarked control pool in the original database instead. + let base = std::env::var(TEST_POSTGRES_URL_ENV) + .expect("OPENSHELL_TEST_POSTGRES_URL must name a disposable PostgreSQL database"); + let admin = PgPoolOptions::new() + .max_connections(1) + .connect(&base) + .await + .expect("connect to the test-control database"); + let database = format!("image_activation_{}", uuid::Uuid::new_v4().simple()); + sqlx::query(sqlx::AssertSqlSafe(format!("CREATE DATABASE {database}"))) + .execute(&admin) + .await + .expect("create a dedicated activation-test database (requires CREATEDB)"); + let mut url = url::Url::parse(&base).expect("parse the test PostgreSQL URL"); + url.set_path(&format!("/{database}")); + let first = Store::connect(url.as_str()).await.unwrap(); + let second = Store::connect(url.as_str()).await.unwrap(); + + // Exercise both data pools and a lazily opened mutation-lock pool. + let guard = second + .acquire_distributed_mutation_guard( + &MutationLockSet::sandbox_lifecycle("activation-test-sandbox"), + tokio::time::Instant::now() + Duration::from_secs(10), + ) + .await + .unwrap(); + first + .ensure_image_update_deployment_compatible() + .await + .expect("compatible data and lock pools must pass despite the other database's control pool"); + drop(guard); + + // No membership record is needed to detect an old or administrative + // client. Do not weaken the production filter to exempt this session. + let legacy_options = url + .as_str() + .parse::() + .unwrap() + .application_name("legacy-image-activation-test"); + let legacy = PgPoolOptions::new() + .max_connections(1) + .connect_with(legacy_options) + .await + .unwrap(); + assert!(matches!( + first.ensure_image_update_deployment_compatible().await, + Err(PersistenceError::Config(_)) + )); + legacy.close().await; + + // SQLx closing its socket can precede the server removing the backend. + // Bound this wait instead of depending on a fixed scheduling delay. + tokio::time::timeout(Duration::from_secs(5), async { + loop { + match first.ensure_image_update_deployment_compatible().await { + Ok(()) => break, + Err(PersistenceError::Config(_)) => { + tokio::time::sleep(Duration::from_millis(20)).await; + } + Err(error) => panic!("compatibility read failed: {error}"), + } + } + }) + .await + .expect("closed legacy backend must disappear from pg_stat_activity"); + + let member = first + .put_if( + MEMBER_OBJECT_TYPE, + "gateway-member:legacy", + "legacy", + "", + br#"{"replica_id":"legacy"}"#, + None, + WriteCondition::MustCreate, + ) + .await + .unwrap(); + assert!(matches!( + first.ensure_image_update_deployment_compatible().await, + Err(PersistenceError::Config(_)) + )); + first + .put_if( + MEMBER_OBJECT_TYPE, + "gateway-member:legacy", + "legacy", + "", + br#"{"replica_id":"legacy","image_update_protocol":2}"#, + None, + WriteCondition::MatchResourceVersion(member.resource_version), + ) + .await + .unwrap(); + first + .ensure_image_update_deployment_compatible() + .await + .unwrap(); + + first.close().await; + assert!(matches!( + first.ensure_image_update_deployment_compatible().await, + Err(PersistenceError::Database(_)) + )); + second.close().await; + sqlx::query(sqlx::AssertSqlSafe(format!( + "DROP DATABASE {database} WITH (FORCE)" + ))) + .execute(&admin) + .await + .expect("drop the isolated activation-test database"); + admin.close().await; + } + + #[test] + fn compatible_live_members_and_empty_snapshot_pass() { + assert!(ensure_live_members_compatible(&[], 100).is_ok()); + let member = record( + br#"{"replica_id":"gw-0","peer_endpoint":"https://gw-0:8443","image_update_protocol":2}"#, + 100, + ); + assert!(ensure_live_members_compatible(&[member], 100).is_ok()); + } + + #[test] + fn malformed_missing_unknown_or_ill_typed_protocols_fail_closed() { + let invalid: &[&[u8]] = &[ + b"not JSON", + br"[]", + br#"["gw-0",1]"#, + br"null", + br#"{"replica_id":"gw-0"}"#, + br#"{"replica_id":"gw-0","image_update_protocol":0}"#, + br#"{"replica_id":"gw-0","image_update_protocol":1}"#, + br#"{"replica_id":"gw-0","image_update_protocol":"1"}"#, + br#"{"replica_id":"gw-0","image_update_protocol":true}"#, + br#"{"replica_id":"gw-0","image_update_protocol":2.0}"#, + br#"{"image_update_protocol":1}"#, + br#"{"replica_id":"","image_update_protocol":1}"#, + br#"{"replica_id":" ","image_update_protocol":1}"#, + ]; + for payload in invalid { + assert!(ensure_live_members_compatible(&[record(payload, 100)], 100).is_err()); + } + } + + #[test] + fn expired_records_are_not_considered_live_but_future_timestamps_are() { + let ttl_ms = i64::try_from(MEMBER_TTL.as_millis()).unwrap(); + assert!(ensure_live_members_compatible(&[record(b"legacy", 100)], 100 + ttl_ms).is_ok()); + assert!( + ensure_live_members_compatible(&[record(b"legacy", 100)], 100 + ttl_ms - 1).is_err() + ); + assert!(ensure_live_members_compatible(&[record(b"legacy", 200)], 100).is_err()); + } + + #[test] + fn any_incompatible_live_member_rejects_the_snapshot() { + let compatible = record(br#"{"replica_id":"gw-0","image_update_protocol":2}"#, 100); + let legacy = record(br#"{"replica_id":"gw-1"}"#, 100); + assert!(ensure_live_members_compatible(&[compatible, legacy], 100).is_err()); + } + + #[test] + fn listing_truncation_fails_closed_even_when_extra_rows_are_expired() { + let records = vec![record(b"legacy", 0); MEMBER_LIMIT as usize + 1]; + let now = i64::try_from(MEMBER_TTL.as_millis()).unwrap(); + assert!(ensure_live_members_compatible(&records, now).is_err()); + } +} diff --git a/crates/openshell-server/src/persistence/mod.rs b/crates/openshell-server/src/persistence/mod.rs index 929e1e72e2..470a7a5de6 100644 --- a/crates/openshell-server/src/persistence/mod.rs +++ b/crates/openshell-server/src/persistence/mod.rs @@ -3,12 +3,14 @@ //! Persistence layer for `OpenShell` Server. +mod image_activation; mod legacy_time_wire; pub mod lock_order; pub mod mutation_lock; mod postgres; #[cfg(test)] mod refresh_lock_tests; +mod sandbox_image; mod sqlite; pub use crate::storage_proto::{ diff --git a/crates/openshell-server/src/persistence/postgres.rs b/crates/openshell-server/src/persistence/postgres.rs index ad5b2bec4c..5b577853fa 100644 --- a/crates/openshell-server/src/persistence/postgres.rs +++ b/crates/openshell-server/src/persistence/postgres.rs @@ -20,7 +20,7 @@ use openshell_core::SetResourceVersion; use openshell_core::proto::Sandbox; use prost::Message; use sqlx::pool::PoolConnection; -use sqlx::postgres::PgPoolOptions; +use sqlx::postgres::{PgConnection, PgPoolOptions}; use sqlx::{Connection, PgPool, Postgres, QueryBuilder, Row}; use std::sync::Arc; use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; @@ -28,6 +28,15 @@ use std::time::Duration; static POSTGRES_MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("./migrations/postgres"); +/// A protocol capability, not an exact build version or an authorization +/// credential. Identify every pool connection, including after reconnect. +async fn identify_image_update_writer(connection: &mut PgConnection) -> Result<(), sqlx::Error> { + sqlx::query("SELECT set_config('application_name', 'openshell-image-update-v2', false)") + .execute(connection) + .await?; + Ok(()) +} + #[cfg(test)] pub(super) fn embedded_migration_sql(version: i64) -> Option<&'static str> { POSTGRES_MIGRATOR @@ -40,7 +49,7 @@ use super::{DELETE_MANY_BATCH_SIZE, DRAFT_CHUNK_OBJECT_TYPE, POLICY_OBJECT_TYPE} #[derive(Debug, Clone)] pub struct PostgresStore { - pool: PgPool, + pub(super) pool: PgPool, /// Dedicated connections for session-level mutation advisory locks. lock_pool: PgPool, /// How acquisitions and guards use the lock pool's connections. @@ -304,6 +313,7 @@ impl PostgresStore { ); let pool = PgPoolOptions::new() .max_connections(max_connections) + .after_connect(|connection, _| Box::pin(identify_image_update_writer(connection))) .connect(url) .await .map_err(|e| map_db_error(&e))?; @@ -320,6 +330,7 @@ impl PostgresStore { .bind(MUTATION_LOCK_TIMEOUT_SETTING) .execute(&mut *connection) .await?; + identify_image_update_writer(connection).await?; Ok(()) }) }) diff --git a/crates/openshell-server/src/persistence/sandbox_image.rs b/crates/openshell-server/src/persistence/sandbox_image.rs new file mode 100644 index 0000000000..11ed0294f5 --- /dev/null +++ b/crates/openshell-server/src/persistence/sandbox_image.rs @@ -0,0 +1,1516 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Private, atomic image-update intent storage. +//! +//! The intent is deliberately not part of `Sandbox`: an older gateway decoding +//! and rewriting that public message must not erase an accepted update. Every +//! transaction locks the sandbox first, then its independent image-state row. + +use super::{PersistenceError, PersistenceResult, Store, current_time_ms, map_db_error}; +use crate::storage_proto::{StoredSandboxImageState, StoredSandboxImageUpdate}; +use openshell_core::SetResourceVersion; +use openshell_core::proto::{Sandbox, SandboxPhase, SandboxProvisioning}; +use prost::Message; +use sqlx::{Postgres, Sqlite, Transaction}; + +const IMAGE_STATE_OBJECT_TYPE: &str = "sandbox_image_state"; + +fn state_id(sandbox_id: &str) -> String { + format!("sandbox-image-state:{sandbox_id}") +} + +fn conflict(current_resource_version: Option) -> PersistenceError { + PersistenceError::Conflict { + current_resource_version, + } +} + +fn decode_state(sandbox_id: &str, payload: &[u8]) -> PersistenceResult { + let state = StoredSandboxImageState::decode(payload) + .map_err(|error| PersistenceError::Decode(error.to_string()))?; + if state.sandbox_id != sandbox_id + || state + .pending + .as_ref() + .is_some_and(|pending| pending.revision == 0 || pending.revision != state.revision) + || (state.pending.is_none() && state.previous_provisioning.is_some()) + || state + .previous_provisioning + .as_ref() + .is_some_and(|previous| previous.driver_operation_pending) + { + return Err(PersistenceError::Database( + "inconsistent stored sandbox image state".into(), + )); + } + Ok(state) +} + +fn decode_sandbox( + sandbox_id: &str, + payload: &[u8], + resource_version: i64, +) -> PersistenceResult { + if resource_version < 1 { + return Err(PersistenceError::Database( + "invalid sandbox resource version".into(), + )); + } + let payload = super::legacy_time_wire::migrate("sandbox", payload)?; + let mut sandbox = Sandbox::decode(payload.as_slice()) + .map_err(|error| PersistenceError::Decode(error.to_string()))?; + if sandbox + .metadata + .as_ref() + .is_none_or(|meta| meta.id != sandbox_id) + { + return Err(PersistenceError::Database( + "inconsistent stored sandbox identity".into(), + )); + } + // The SQL column, not a possibly stale protobuf field, is authoritative. + sandbox.set_resource_version(resource_version.cast_unsigned()); + Ok(sandbox) +} + +fn resource_version(sandbox: &Sandbox) -> u64 { + sandbox + .metadata + .as_ref() + .map_or(0, |meta| meta.resource_version) +} + +fn require_stopped(sandbox: &Sandbox) -> PersistenceResult<()> { + if sandbox + .metadata + .as_ref() + .is_none_or(|meta| meta.deletion_time.is_some()) + || sandbox + .status + .as_ref() + .is_none_or(|status| status.phase != SandboxPhase::Stopped as i32) + { + return Err(conflict(Some(resource_version(sandbox)))); + } + Ok(()) +} + +fn validate_intent(intent: &StoredSandboxImageUpdate) -> PersistenceResult<()> { + for map in [intent.labels.as_ref(), intent.annotations.as_ref()] + .into_iter() + .flatten() + { + if map + .values + .keys() + .any(|key| openshell_core::sandbox_update::is_system_metadata_key(key)) + { + return Err(PersistenceError::Encode( + "system metadata cannot be updated".into(), + )); + } + } + if intent.operation_id.is_empty() + || intent.previous_image.is_empty() + || intent.image.is_empty() + || intent.revision == 0 + { + return Err(PersistenceError::Encode( + "image update requires an operation id, both images, and a nonzero revision".into(), + )); + } + Ok(()) +} + +fn bump_resource_version(sandbox: &mut Sandbox) -> PersistenceResult<()> { + let next = resource_version(sandbox) + .checked_add(1) + .filter(|next| i64::try_from(*next).is_ok()) + .ok_or_else(|| PersistenceError::Encode("sandbox resource version exhausted".into()))?; + sandbox.set_resource_version(next); + Ok(()) +} + +/// Pure projection: all acceptance checks happen before either row is written. +fn project_begin( + mut sandbox: Sandbox, + state: Option, + expected_sandbox_resource_version: u64, + expected_revision: u64, + intent: &StoredSandboxImageUpdate, +) -> PersistenceResult<(Sandbox, StoredSandboxImageState)> { + let current_version = resource_version(&sandbox); + if expected_sandbox_resource_version == 0 + || expected_sandbox_resource_version != current_version + { + return Err(conflict(Some(current_version))); + } + require_stopped(&sandbox)?; + validate_intent(intent)?; + let mut state = state.unwrap_or_else(|| StoredSandboxImageState { + sandbox_id: sandbox + .metadata + .as_ref() + .map_or_else(String::new, |meta| meta.id.clone()), + revision: 0, + pending: None, + previous_provisioning: None, + }); + if state.revision != expected_revision || state.pending.is_some() { + return Err(conflict(Some(current_version))); + } + let next_revision = expected_revision + .checked_add(1) + .ok_or_else(|| PersistenceError::Encode("sandbox image revision exhausted".into()))?; + if intent.revision != next_revision { + return Err(conflict(Some(current_version))); + } + let previous_provisioning = sandbox + .status + .as_ref() + .and_then(|status| status.provisioning.clone()); + if previous_provisioning + .as_ref() + .is_some_and(|previous| previous.driver_operation_pending) + { + return Err(conflict(Some(current_version))); + } + let template = sandbox + .spec + .as_mut() + .and_then(|spec| spec.template.as_mut()) + .ok_or_else(|| conflict(Some(current_version)))?; + if template.image != intent.previous_image { + return Err(conflict(Some(current_version))); + } + template.image.clone_from(&intent.image); + openshell_core::sandbox_update::apply_configuration( + &mut sandbox, + intent.command.as_ref(), + intent.environment.as_ref(), + intent.labels.as_ref(), + intent.annotations.as_ref(), + ); + state.revision = next_revision; + state.pending = Some(intent.clone()); + state.previous_provisioning = previous_provisioning; + let status = sandbox + .status + .as_mut() + .ok_or_else(|| conflict(Some(current_version)))?; + let operation = status + .provisioning + .get_or_insert_with(|| SandboxProvisioning { + attempt_id: format!("image-update:{}", intent.operation_id), + ..Default::default() + }); + // This is an existing, baseline-known lifecycle interlock. Old gateways + // retain it on ordinary status writes and refuse Start/Stop/Delete while + // it is pending. Do not settle it on an ambiguous driver error. + operation.driver_operation_pending = true; + operation + .driver_operation_id + .clone_from(&intent.operation_id); + bump_resource_version(&mut sandbox)?; + Ok((sandbox, state)) +} + +fn project_complete( + mut sandbox: Sandbox, + state: Option, + intent: &StoredSandboxImageUpdate, +) -> PersistenceResult<(Sandbox, StoredSandboxImageState)> { + let current_version = resource_version(&sandbox); + require_stopped(&sandbox)?; + validate_intent(intent)?; + let mut state = state.ok_or_else(|| conflict(Some(current_version)))?; + if state.revision != intent.revision || state.pending.as_ref() != Some(intent) { + return Err(conflict(Some(current_version))); + } + if sandbox + .spec + .as_ref() + .and_then(|spec| spec.template.as_ref()) + .is_none_or(|template| template.image != intent.image) + { + return Err(conflict(Some(current_version))); + } + // Acceptance already committed the annotation replacement. UpdateConfig + // may subsequently merge annotations while driver reconciliation is pending; + // preserve those accepted writes rather than requiring the old map forever. + // Annotations are gateway metadata and are not reconciled by the driver. + let mut target = sandbox.clone(); + openshell_core::sandbox_update::apply_configuration( + &mut target, + intent.command.as_ref(), + intent.environment.as_ref(), + intent.labels.as_ref(), + None, + ); + if target != sandbox { + return Err(conflict(Some(current_version))); + } + let status = sandbox + .status + .as_mut() + .ok_or_else(|| conflict(Some(current_version)))?; + let expected_attempt = state.previous_provisioning.as_ref().map_or_else( + || format!("image-update:{}", intent.operation_id), + |previous| previous.attempt_id.clone(), + ); + let operation = status + .provisioning + .as_mut() + .ok_or_else(|| conflict(Some(current_version)))?; + if !operation.driver_operation_pending + || operation.driver_operation_id != intent.operation_id + || operation.attempt_id != expected_attempt + { + return Err(conflict(Some(current_version))); + } + // Retire only our flag on the CURRENT record. Restoring a saved snapshot + // would erase a concurrent provider attachment's accepted clock edit. + // Keep the newest operation ID to reject delayed older callbacks (ABA). + let unchanged_legacy_record = state.previous_provisioning.is_none() + && *operation + == SandboxProvisioning { + attempt_id: expected_attempt, + driver_operation_id: intent.operation_id.clone(), + driver_operation_pending: true, + ..Default::default() + }; + if unchanged_legacy_record { + status.provisioning = None; + } else { + operation.driver_operation_pending = false; + } + state.previous_provisioning = None; + state.pending = None; + // Completing an intent invalidates any Start CAS based on pending state. + bump_resource_version(&mut sandbox)?; + Ok((sandbox, state)) +} + +impl Store { + /// Remove private image state when its owning sandbox is reclaimed. + pub(crate) async fn delete_sandbox_image_state( + &self, + sandbox_id: &str, + ) -> PersistenceResult { + self.delete(IMAGE_STATE_OBJECT_TYPE, &state_id(sandbox_id)) + .await + } + + /// Read gateway-private image state by immutable sandbox identity. + pub async fn get_sandbox_image_state( + &self, + sandbox_id: &str, + ) -> PersistenceResult> { + let id = state_id(sandbox_id); + let payload = match self { + Self::Sqlite(store) => { + sqlx::query_scalar::<_, Vec>( + r#"SELECT "payload" FROM "objects" WHERE "object_type" = ?1 AND "id" = ?2"#, + ) + .bind(IMAGE_STATE_OBJECT_TYPE) + .bind(&id) + .fetch_optional(&store.pool) + .await + } + Self::Postgres(store) => { + sqlx::query_scalar::<_, Vec>( + "SELECT payload FROM objects WHERE object_type = $1 AND id = $2", + ) + .bind(IMAGE_STATE_OBJECT_TYPE) + .bind(&id) + .fetch_optional(&store.pool) + .await + } + } + .map_err(|error| map_db_error(&error))?; + payload + .map(|payload| decode_state(sandbox_id, &payload)) + .transpose() + } + + /// Atomically accept a stopped-sandbox image change and its private intent. + pub async fn begin_sandbox_image_update( + &self, + sandbox_id: &str, + expected_sandbox_resource_version: u64, + expected_revision: u64, + intent: &StoredSandboxImageUpdate, + ) -> PersistenceResult { + let mut tx = ImageTransaction::begin(self).await?; + let sandbox = tx.lock_sandbox(sandbox_id).await?; + let state = tx.lock_state(sandbox_id).await?; + let previous_version = resource_version(&sandbox); + let (sandbox, state) = project_begin( + sandbox, + state, + expected_sandbox_resource_version, + expected_revision, + intent, + )?; + tx.write_projection(sandbox_id, previous_version, &sandbox, &state) + .await?; + tx.commit().await?; + Ok(sandbox) + } + + /// Retire only the exact pending intent, retaining its monotonic revision. + pub async fn complete_sandbox_image_update( + &self, + sandbox_id: &str, + intent: &StoredSandboxImageUpdate, + ) -> PersistenceResult { + let mut tx = ImageTransaction::begin(self).await?; + let sandbox = tx.lock_sandbox(sandbox_id).await?; + let state = tx.lock_state(sandbox_id).await?; + let previous_version = resource_version(&sandbox); + let (sandbox, state) = project_complete(sandbox, state, intent)?; + tx.write_projection(sandbox_id, previous_version, &sandbox, &state) + .await?; + tx.commit().await?; + Ok(sandbox) + } +} + +/// Dropping an unfinished `SQLx` transaction rolls back both projections. +enum ImageTransaction { + Sqlite(Transaction<'static, Sqlite>), + Postgres(Transaction<'static, Postgres>), +} + +impl ImageTransaction { + async fn begin(store: &Store) -> PersistenceResult { + match store { + Store::Sqlite(store) => store + .pool + .begin_with("BEGIN IMMEDIATE") + .await + .map(Self::Sqlite), + Store::Postgres(store) => store.pool.begin().await.map(Self::Postgres), + } + .map_err(|error| map_db_error(&error)) + } + + async fn lock_sandbox(&mut self, sandbox_id: &str) -> PersistenceResult { + let row = match self { + Self::Sqlite(tx) => { + sqlx::query_as::<_, (Vec, i64)>( + r#"SELECT "payload", "resource_version" FROM "objects" +WHERE "object_type" = 'sandbox' AND "id" = ?1"#, + ) + .bind(sandbox_id) + .fetch_optional(&mut **tx) + .await + } + Self::Postgres(tx) => { + sqlx::query_as::<_, (Vec, i64)>( + "SELECT payload, resource_version FROM objects +WHERE object_type = 'sandbox' AND id = $1 FOR UPDATE", + ) + .bind(sandbox_id) + .fetch_optional(&mut **tx) + .await + } + } + .map_err(|error| map_db_error(&error))? + .ok_or_else(|| conflict(None))?; + decode_sandbox(sandbox_id, &row.0, row.1) + } + + async fn lock_state( + &mut self, + sandbox_id: &str, + ) -> PersistenceResult> { + let id = state_id(sandbox_id); + let payload = match self { + Self::Sqlite(tx) => { + sqlx::query_scalar::<_, Vec>( + r#"SELECT "payload" FROM "objects" WHERE "object_type" = ?1 AND "id" = ?2"#, + ) + .bind(IMAGE_STATE_OBJECT_TYPE) + .bind(&id) + .fetch_optional(&mut **tx) + .await + } + Self::Postgres(tx) => { + sqlx::query_scalar::<_, Vec>( + "SELECT payload FROM objects WHERE object_type = $1 AND id = $2 FOR UPDATE", + ) + .bind(IMAGE_STATE_OBJECT_TYPE) + .bind(&id) + .fetch_optional(&mut **tx) + .await + } + } + .map_err(|error| map_db_error(&error))?; + payload + .map(|payload| decode_state(sandbox_id, &payload)) + .transpose() + } + + async fn write_projection( + &mut self, + sandbox_id: &str, + previous_version: u64, + sandbox: &Sandbox, + state: &StoredSandboxImageState, + ) -> PersistenceResult<()> { + let previous_version_sql = i64::try_from(previous_version).map_err(|_| { + PersistenceError::Encode("sandbox resource version exceeds database range".into()) + })?; + let now_ms = current_time_ms(); + let sandbox_payload = sandbox.encode_to_vec(); + let labels = sandbox.metadata.as_ref().map(|metadata| &metadata.labels); + let labels = serde_json::to_value(labels) + .map_err(|error| PersistenceError::Encode(error.to_string()))?; + let affected = match self { + Self::Sqlite(tx) => sqlx::query( + r#"UPDATE "objects" +SET "payload" = ?2, "updated_at_ms" = ?3, "resource_version" = "resource_version" + 1, "labels" = ?5 +WHERE "object_type" = 'sandbox' AND "id" = ?1 AND "resource_version" = ?4"#, + ) + .bind(sandbox_id) + .bind(&sandbox_payload) + .bind(now_ms) + .bind(previous_version_sql) + .bind(labels.to_string()) + .execute(&mut **tx) + .await + .map(|result| result.rows_affected()), + Self::Postgres(tx) => sqlx::query( + "UPDATE objects +SET payload = $2, updated_at_ms = $3, resource_version = resource_version + 1, labels = $5 +WHERE object_type = 'sandbox' AND id = $1 AND resource_version = $4", + ) + .bind(sandbox_id) + .bind(&sandbox_payload) + .bind(now_ms) + .bind(previous_version_sql) + .bind(&labels) + .execute(&mut **tx) + .await + .map(|result| result.rows_affected()), + } + .map_err(|error| map_db_error(&error))?; + if affected != 1 { + return Err(conflict(Some(previous_version))); + } + + let id = state_id(sandbox_id); + let state_payload = state.encode_to_vec(); + let affected = match self { + Self::Sqlite(tx) => sqlx::query( + r#"INSERT INTO "objects" ( + "object_type", "id", "name", "workspace", "payload", "created_at_ms", "updated_at_ms" +) VALUES (?1, ?2, NULL, '', ?3, ?4, ?4) +ON CONFLICT ("id") DO UPDATE +SET "payload" = excluded."payload", "updated_at_ms" = excluded."updated_at_ms", + "resource_version" = "objects"."resource_version" + 1 +WHERE "objects"."object_type" = ?1 AND "objects"."name" IS NULL AND "objects"."workspace" = ''"#, + ) + .bind(IMAGE_STATE_OBJECT_TYPE) + .bind(&id) + .bind(&state_payload) + .bind(now_ms) + .execute(&mut **tx) + .await + .map(|result| result.rows_affected()), + Self::Postgres(tx) => sqlx::query( + "INSERT INTO objects ( + object_type, id, name, workspace, payload, created_at_ms, updated_at_ms +) VALUES ($1, $2, NULL, '', $3, $4, $4) +ON CONFLICT (id) DO UPDATE +SET payload = EXCLUDED.payload, updated_at_ms = EXCLUDED.updated_at_ms, + resource_version = objects.resource_version + 1 +WHERE objects.object_type = $1 AND objects.name IS NULL AND objects.workspace = ''", + ) + .bind(IMAGE_STATE_OBJECT_TYPE) + .bind(&id) + .bind(&state_payload) + .bind(now_ms) + .execute(&mut **tx) + .await + .map(|result| result.rows_affected()), + } + .map_err(|error| map_db_error(&error))?; + if affected != 1 { + return Err(PersistenceError::Database( + "sandbox image state identity is already occupied".into(), + )); + } + Ok(()) + } + + async fn commit(self) -> PersistenceResult<()> { + match self { + Self::Sqlite(tx) => tx.commit().await, + Self::Postgres(tx) => tx.commit().await, + } + .map_err(|error| map_db_error(&error)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use openshell_core::proto::{ObjectMeta, SandboxSpec, SandboxStatus, SandboxTemplate}; + + fn sandbox() -> Sandbox { + Sandbox { + metadata: Some(ObjectMeta { + id: "sandbox-id".into(), + name: "sandbox-name".into(), + workspace: "default".into(), + resource_version: 5, + ..Default::default() + }), + spec: Some(SandboxSpec { + template: Some(SandboxTemplate { + image: "old-image".into(), + ..Default::default() + }), + ..Default::default() + }), + status: Some(SandboxStatus { + phase: SandboxPhase::Stopped as i32, + ..Default::default() + }), + ..Default::default() + } + } + + fn intent() -> StoredSandboxImageUpdate { + StoredSandboxImageUpdate { + operation_id: "operation-id".into(), + previous_image: "old-image".into(), + image: "new-image".into(), + revision: 1, + ..Default::default() + } + } + + fn image(sandbox: &Sandbox) -> &str { + &sandbox + .spec + .as_ref() + .unwrap() + .template + .as_ref() + .unwrap() + .image + } + + #[test] + fn begin_and_complete_project_image_and_baseline_known_interlock() { + let original = sandbox(); + let (accepted, state) = project_begin(original.clone(), None, 5, 0, &intent()).unwrap(); + assert_eq!(image(&accepted), "new-image"); + assert_eq!(resource_version(&accepted), 6); + let operation = accepted + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap(); + assert!(operation.driver_operation_pending); + assert_eq!(operation.driver_operation_id, intent().operation_id); + assert_eq!(operation.attempt_id, "image-update:operation-id"); + assert!(operation.deadline.is_none()); + assert!(operation.preparation_deadline.is_none()); + assert!(state.previous_provisioning.is_none()); + assert_eq!(state.pending, Some(intent())); + let (completed, state) = project_complete(accepted, Some(state), &intent()).unwrap(); + assert_eq!(resource_version(&completed), 7); + assert_eq!(completed.status, original.status); + assert_eq!(state.revision, 1); + assert!(state.pending.is_none()); + assert!(state.previous_provisioning.is_none()); + } + + #[test] + fn completion_restores_existing_provisioning_except_last_operation_identity() { + let mut original = sandbox(); + let previous = SandboxProvisioning { + attempt_id: "previous-attempt".into(), + driver_operation_id: "previous-settled-operation".into(), + timeout_time: Some(prost_types::Timestamp { + seconds: 123, + nanos: 456, + }), + cleanup_completed_time: Some(prost_types::Timestamp { + seconds: 124, + nanos: 0, + }), + cleanup_error: "retained diagnostic".into(), + ..Default::default() + }; + original.status.as_mut().unwrap().provisioning = Some(previous.clone()); + let (accepted, state) = project_begin(original.clone(), None, 5, 0, &intent()).unwrap(); + assert_eq!(state.previous_provisioning, Some(previous)); + let (completed, cleared) = project_complete(accepted, Some(state), &intent()).unwrap(); + original + .status + .as_mut() + .unwrap() + .provisioning + .as_mut() + .unwrap() + .driver_operation_id = intent().operation_id; + assert_eq!(completed.status, original.status); + assert!(cleared.previous_provisioning.is_none()); + assert!(cleared.pending.is_none()); + assert_eq!(cleared.revision, 1); + } + + #[test] + fn delayed_previous_operation_cannot_match_completed_image_operation() { + let mut original = sandbox(); + let previous = SandboxProvisioning { + attempt_id: "retained-attempt".into(), + driver_operation_id: "delayed-operation".into(), + timeout_time: Some(prost_types::Timestamp { + seconds: 456, + nanos: 0, + }), + cleanup_completed_time: Some(prost_types::Timestamp { + seconds: 457, + nanos: 0, + }), + ..Default::default() + }; + original.status.as_mut().unwrap().provisioning = Some(previous.clone()); + let (accepted, state) = project_begin(original, None, 5, 0, &intent()).unwrap(); + let (completed, _) = project_complete(accepted, Some(state), &intent()).unwrap(); + let current = completed + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap(); + // Baseline same_operation compares this pair. Preserve the attempt and + // timeout provenance without resurrecting the old callback's identity. + assert_ne!( + (¤t.attempt_id, ¤t.driver_operation_id), + (&previous.attempt_id, &previous.driver_operation_id) + ); + assert_eq!(current.attempt_id, previous.attempt_id); + assert_eq!(current.driver_operation_id, intent().operation_id); + assert!(!current.driver_operation_pending); + assert_eq!(current.timeout_time, previous.timeout_time); + assert_eq!( + current.cleanup_completed_time, + previous.cleanup_completed_time + ); + } + + #[test] + fn image_completion_preserves_concurrent_attachment_clock_edits() { + let mut original = sandbox(); + let mut previous = crate::compute::provisioning_deadline::new_preparation_record(0, 1800); + previous.driver_operation_id = "prior-operation".into(); + previous.attachment_change_id = "before-attachment".into(); + original.status.as_mut().unwrap().provisioning = Some(previous); + let (mut accepted, state) = project_begin(original, None, 5, 0, &intent()).unwrap(); + crate::compute::provisioning_deadline::attachments_changed(&mut accepted, 2_000); + let mut expected = accepted + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap() + .clone(); + assert_ne!(expected.attachment_change_id, "before-attachment"); + expected.driver_operation_pending = false; + let (completed, state) = project_complete(accepted, Some(state), &intent()).unwrap(); + assert_eq!(completed.status.unwrap().provisioning, Some(expected)); + assert!(state.previous_provisioning.is_none()); + } + + #[test] + fn image_completion_cannot_settle_a_replaced_provisioning_attempt() { + let (mut accepted, state) = project_begin(sandbox(), None, 5, 0, &intent()).unwrap(); + accepted + .status + .as_mut() + .unwrap() + .provisioning + .as_mut() + .unwrap() + .attempt_id = "replacement-attempt".into(); + assert!(matches!( + project_complete(accepted, Some(state), &intent()), + Err(PersistenceError::Conflict { .. }) + )); + } + + #[test] + fn pending_driver_operation_is_not_overwritten_and_completion_requires_its_fence() { + let mut occupied = sandbox(); + occupied.status.as_mut().unwrap().provisioning = Some(SandboxProvisioning { + attempt_id: "other-attempt".into(), + driver_operation_pending: true, + driver_operation_id: "other-operation".into(), + ..Default::default() + }); + assert!(project_begin(occupied, None, 5, 0, &intent()).is_err()); + let (accepted, state) = project_begin(sandbox(), None, 5, 0, &intent()).unwrap(); + let mut missing = accepted.clone(); + missing.status.as_mut().unwrap().provisioning = None; + assert!(project_complete(missing, Some(state.clone()), &intent()).is_err()); + let mut cleared = accepted.clone(); + cleared + .status + .as_mut() + .unwrap() + .provisioning + .as_mut() + .unwrap() + .driver_operation_pending = false; + assert!(project_complete(cleared, Some(state.clone()), &intent()).is_err()); + let mut other = accepted; + other + .status + .as_mut() + .unwrap() + .provisioning + .as_mut() + .unwrap() + .driver_operation_id = "other".into(); + assert!(project_complete(other, Some(state), &intent()).is_err()); + } + + #[test] + fn begin_rejects_stale_versions_revisions_and_pending_updates() { + for version in [0, 4, 6] { + assert!(matches!( + project_begin(sandbox(), None, version, 0, &intent()), + Err(PersistenceError::Conflict { .. }) + )); + } + assert!(project_begin(sandbox(), None, 5, 1, &intent()).is_err()); + let mut wrong = intent(); + wrong.revision = 2; + assert!(project_begin(sandbox(), None, 5, 0, &wrong).is_err()); + let (accepted, state) = project_begin(sandbox(), None, 5, 0, &intent()).unwrap(); + let next = StoredSandboxImageUpdate { + operation_id: "next-operation".into(), + previous_image: "new-image".into(), + image: "newer-image".into(), + revision: 2, + ..Default::default() + }; + assert!(project_begin(accepted, Some(state), 6, 1, &next).is_err()); + } + + #[test] + fn lifecycle_and_image_checks_apply_at_acceptance_and_completion() { + let mut running = sandbox(); + running.status.as_mut().unwrap().phase = SandboxPhase::Ready as i32; + assert!(project_begin(running, None, 5, 0, &intent()).is_err()); + let mut deleting = sandbox(); + deleting.metadata.as_mut().unwrap().deletion_time = Some(prost_types::Timestamp::default()); + assert!(project_begin(deleting, None, 5, 0, &intent()).is_err()); + let mut wrong_image = sandbox(); + wrong_image + .spec + .as_mut() + .unwrap() + .template + .as_mut() + .unwrap() + .image = "other".into(); + assert!(project_begin(wrong_image, None, 5, 0, &intent()).is_err()); + let (accepted, state) = project_begin(sandbox(), None, 5, 0, &intent()).unwrap(); + let mut stale_intent = intent(); + stale_intent.operation_id = "other-operation".into(); + assert!(project_complete(accepted.clone(), Some(state.clone()), &stale_intent).is_err()); + let mut changed = accepted.clone(); + changed + .spec + .as_mut() + .unwrap() + .template + .as_mut() + .unwrap() + .image = "other".into(); + assert!(project_complete(changed, Some(state.clone()), &intent()).is_err()); + let mut running = accepted.clone(); + running.status.as_mut().unwrap().phase = SandboxPhase::Starting as i32; + assert!(project_complete(running, Some(state.clone()), &intent()).is_err()); + let mut deleting = accepted.clone(); + deleting.metadata.as_mut().unwrap().deletion_time = Some(prost_types::Timestamp::default()); + assert!(project_complete(deleting, Some(state.clone()), &intent()).is_err()); + let mut wrong_revision = state; + wrong_revision.revision = 2; + assert!(project_complete(accepted.clone(), Some(wrong_revision), &intent()).is_err()); + assert!(project_complete(accepted, None, &intent()).is_err()); + } + + #[test] + fn sandbox_decode_uses_authoritative_column_and_rejects_invalid_identity() { + let original = sandbox(); + let decoded = decode_sandbox("sandbox-id", &original.encode_to_vec(), 12).unwrap(); + assert_eq!(resource_version(&decoded), 12); + assert!(decode_sandbox("other-id", &original.encode_to_vec(), 12).is_err()); + assert!(decode_sandbox("sandbox-id", &original.encode_to_vec(), 0).is_err()); + } + + #[test] + fn resource_version_overflow_and_invalid_intents_do_not_project() { + let mut exhausted = sandbox(); + exhausted.set_resource_version(i64::MAX.cast_unsigned()); + assert!(matches!( + project_begin(exhausted, None, i64::MAX.cast_unsigned(), 0, &intent()), + Err(PersistenceError::Encode(_)) + )); + let mut invalid = intent(); + invalid.operation_id.clear(); + assert!(matches!( + project_begin(sandbox(), None, 5, 0, &invalid), + Err(PersistenceError::Encode(_)) + )); + } + + async fn fixture() -> (Store, Sandbox) { + let store = super::super::test_store().await; + store.put_message(&sandbox()).await.unwrap(); + let stored = store + .get_message::("sandbox-id") + .await + .unwrap() + .unwrap(); + (store, stored) + } + + #[test] + fn legacy_image_intent_decodes_without_configuration_selections() { + let decoded = + StoredSandboxImageUpdate::decode(&b"\x0a\x02op\x12\x01a\x1a\x01b\x20\x01"[..]).unwrap(); + assert_eq!(decoded.operation_id, "op"); + assert_eq!(decoded.previous_image, "a"); + assert_eq!(decoded.image, "b"); + assert_eq!(decoded.revision, 1); + assert!( + decoded.command.is_none() + && decoded.environment.is_none() + && decoded.labels.is_none() + && decoded.annotations.is_none() + ); + } + + #[tokio::test] + async fn sqlite_configuration_update_reindexes_labels_atomically() { + let (store, original) = fixture().await; + assert_configuration_update_reindexes_labels(&store, original).await; + } + + #[tokio::test] + #[ignore = "requires a disposable PostgreSQL database; run mise run test:rust:postgres"] + async fn postgres_sandbox_image_configuration_reindexes_labels_atomically() { + let schema = + super::super::test_postgres::TestSchema::create("sandbox_configuration_labels").await; + let store = schema.connect_store().await; + crate::ensure_default_workspace(&store).await.unwrap(); + store.put_message(&sandbox()).await.unwrap(); + let original = store + .get_message::("sandbox-id") + .await + .unwrap() + .unwrap(); + assert_configuration_update_reindexes_labels(&store, original).await; + store.close().await; + schema.drop_schema().await; + } + + async fn assert_configuration_update_reindexes_labels(store: &Store, mut original: Sandbox) { + use openshell_core::proto::SandboxMapUpdate; + original + .metadata + .as_mut() + .unwrap() + .labels + .insert("team".into(), "old".into()); + store.put_message(&original).await.unwrap(); + let original = store + .get_message::("sandbox-id") + .await + .unwrap() + .unwrap(); + let update = StoredSandboxImageUpdate { + labels: Some(SandboxMapUpdate { + values: std::collections::HashMap::from([("team".into(), "new".into())]), + }), + ..intent() + }; + store + .begin_sandbox_image_update("sandbox-id", resource_version(&original), 0, &update) + .await + .unwrap(); + assert!( + store + .list_with_selector("sandbox", "default", "team=old", 10, 0) + .await + .unwrap() + .is_empty() + ); + assert_eq!( + store + .list_with_selector("sandbox", "default", "team=new", 10, 0) + .await + .unwrap() + .len(), + 1 + ); + store + .complete_sandbox_image_update("sandbox-id", &update) + .await + .unwrap(); + assert_eq!( + store + .list_with_selector("sandbox", "default", "team=new", 10, 0) + .await + .unwrap() + .len(), + 1 + ); + } + + #[tokio::test] + async fn sqlite_accept_complete_and_legacy_rewrite_preserve_independent_state() { + let (store, original) = fixture().await; + let version = resource_version(&original); + let accepted = store + .begin_sandbox_image_update("sandbox-id", version, 0, &intent()) + .await + .unwrap(); + assert_eq!(resource_version(&accepted), version + 1); + assert_eq!(image(&accepted), "new-image"); + let pending = store + .get_sandbox_image_state("sandbox-id") + .await + .unwrap() + .unwrap(); + assert_eq!(pending.pending, Some(intent())); + // Generic Sandbox decode/reencode cannot remove the separate intent. + let decoded = Sandbox::decode(accepted.encode_to_vec().as_slice()).unwrap(); + assert!( + decoded + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap() + .driver_operation_pending + ); + assert_eq!( + decoded + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap() + .driver_operation_id, + intent().operation_id + ); + store.put_message(&decoded).await.unwrap(); + assert_eq!( + store.get_sandbox_image_state("sandbox-id").await.unwrap(), + Some(pending) + ); + let before_complete = store + .get_message::("sandbox-id") + .await + .unwrap() + .unwrap(); + let completed = store + .complete_sandbox_image_update("sandbox-id", &intent()) + .await + .unwrap(); + assert_eq!( + resource_version(&completed), + resource_version(&before_complete) + 1 + ); + let retained = store + .get_sandbox_image_state("sandbox-id") + .await + .unwrap() + .unwrap(); + assert_eq!(retained.revision, 1); + assert!(retained.pending.is_none()); + assert!(retained.previous_provisioning.is_none()); + assert_eq!(completed.status, original.status); + // A Start based on any pre-completion snapshot must lose its CAS. + let mut stale_start = before_complete.clone(); + stale_start.status.as_mut().unwrap().phase = SandboxPhase::Starting as i32; + assert!(matches!( + store + .put_if( + "sandbox", + "sandbox-id", + "sandbox-name", + "default", + &stale_start.encode_to_vec(), + None, + super::super::WriteCondition::MatchResourceVersion(resource_version( + &before_complete + )), + ) + .await, + Err(PersistenceError::Conflict { .. }) + )); + // Neither a completed operation nor an older generation can be replayed. + assert!( + store + .complete_sandbox_image_update("sandbox-id", &intent()) + .await + .is_err() + ); + let next = StoredSandboxImageUpdate { + operation_id: "next-operation".into(), + previous_image: "new-image".into(), + image: "newer-image".into(), + revision: 2, + ..Default::default() + }; + store + .begin_sandbox_image_update("sandbox-id", resource_version(&completed), 1, &next) + .await + .unwrap(); + assert!( + store + .complete_sandbox_image_update("sandbox-id", &intent()) + .await + .is_err() + ); + assert_eq!( + store + .get_sandbox_image_state("sandbox-id") + .await + .unwrap() + .unwrap() + .pending, + Some(next) + ); + } + + #[tokio::test] + async fn sqlite_restores_prior_provisioning_and_wrong_completion_keeps_fence() { + let (store, mut original) = fixture().await; + let previous = SandboxProvisioning { + attempt_id: "timed-out-attempt".into(), + driver_operation_id: "settled-driver-operation".into(), + timeout_time: Some(prost_types::Timestamp { + seconds: 123, + nanos: 0, + }), + cleanup_completed_time: Some(prost_types::Timestamp { + seconds: 124, + nanos: 0, + }), + ..Default::default() + }; + original.status.as_mut().unwrap().provisioning = Some(previous.clone()); + store.put_message(&original).await.unwrap(); + let mut original = store + .get_message::("sandbox-id") + .await + .unwrap() + .unwrap(); + let accepted = store + .begin_sandbox_image_update("sandbox-id", resource_version(&original), 0, &intent()) + .await + .unwrap(); + let mut wrong = intent(); + wrong.operation_id = "stale-operation".into(); + assert!( + store + .complete_sandbox_image_update("sandbox-id", &wrong) + .await + .is_err() + ); + assert_eq!( + store + .get_message::("sandbox-id") + .await + .unwrap() + .unwrap(), + accepted + ); + let state = store + .get_sandbox_image_state("sandbox-id") + .await + .unwrap() + .unwrap(); + assert_eq!(state.pending, Some(intent())); + assert_eq!(state.previous_provisioning, Some(previous.clone())); + let completed = store + .complete_sandbox_image_update("sandbox-id", &intent()) + .await + .unwrap(); + let mut expected = previous; + expected.driver_operation_id = intent().operation_id; + assert_eq!( + completed.status.as_ref().unwrap().provisioning, + Some(expected.clone()) + ); + original.status.as_mut().unwrap().provisioning = Some(expected); + assert_eq!(completed.status, original.status); + assert!( + store + .get_sandbox_image_state("sandbox-id") + .await + .unwrap() + .unwrap() + .previous_provisioning + .is_none() + ); + } + + #[tokio::test] + async fn sqlite_failed_cas_has_no_private_write() { + let (store, original) = fixture().await; + for version in [0, resource_version(&original) + 1] { + assert!(matches!( + store + .begin_sandbox_image_update("sandbox-id", version, 0, &intent()) + .await, + Err(PersistenceError::Conflict { .. }) + )); + } + assert!( + store + .get_sandbox_image_state("sandbox-id") + .await + .unwrap() + .is_none() + ); + assert_eq!( + store + .get_message::("sandbox-id") + .await + .unwrap() + .unwrap(), + original + ); + } + + #[tokio::test] + async fn sqlite_private_write_failure_rolls_back_sandbox_and_completion() { + let (store, original) = fixture().await; + let Store::Sqlite(sqlite) = &store else { + unreachable!() + }; + sqlx::query( + "CREATE TRIGGER fail_image_insert BEFORE INSERT ON objects +WHEN NEW.object_type = 'sandbox_image_state' +BEGIN SELECT RAISE(ABORT, 'injected image state write failure'); END", + ) + .execute(&sqlite.pool) + .await + .unwrap(); + assert!( + store + .begin_sandbox_image_update("sandbox-id", resource_version(&original), 0, &intent()) + .await + .is_err() + ); + assert_eq!( + store + .get_message::("sandbox-id") + .await + .unwrap() + .unwrap(), + original + ); + assert!( + store + .get_sandbox_image_state("sandbox-id") + .await + .unwrap() + .is_none() + ); + sqlx::query("DROP TRIGGER fail_image_insert") + .execute(&sqlite.pool) + .await + .unwrap(); + let accepted = store + .begin_sandbox_image_update("sandbox-id", resource_version(&original), 0, &intent()) + .await + .unwrap(); + let pending = store.get_sandbox_image_state("sandbox-id").await.unwrap(); + sqlx::query( + "CREATE TRIGGER fail_image_update BEFORE UPDATE ON objects +WHEN NEW.object_type = 'sandbox_image_state' +BEGIN SELECT RAISE(ABORT, 'injected image state write failure'); END", + ) + .execute(&sqlite.pool) + .await + .unwrap(); + assert!( + store + .complete_sandbox_image_update("sandbox-id", &intent()) + .await + .is_err() + ); + assert_eq!( + store + .get_message::("sandbox-id") + .await + .unwrap() + .unwrap(), + accepted + ); + assert_eq!( + store.get_sandbox_image_state("sandbox-id").await.unwrap(), + pending + ); + } + + #[tokio::test] + #[ignore = "requires a disposable PostgreSQL database in OPENSHELL_TEST_POSTGRES_URL; run mise run test:rust:postgres"] + async fn postgres_sandbox_image_state_survives_legacy_writes_and_fences_concurrent_updates() { + use super::super::{WriteCondition, test_postgres::TestSchema}; + + let schema = TestSchema::create("sandbox_image").await; + let first = schema.connect_store().await; + let second = schema.connect_store().await; + crate::ensure_default_workspace(&first).await.unwrap(); + first.put_message(&sandbox()).await.unwrap(); + let original = second + .get_message::("sandbox-id") + .await + .unwrap() + .unwrap(); + let version = resource_version(&original); + let left = intent(); + let right = StoredSandboxImageUpdate { + operation_id: "competing-operation".into(), + image: "competing-image".into(), + ..intent() + }; + + // Independent pools contend on the same sandbox row; exactly one + // transaction may publish revision 1 and its matching operation fence. + let (left_result, right_result) = tokio::join!( + first.begin_sandbox_image_update("sandbox-id", version, 0, &left), + second.begin_sandbox_image_update("sandbox-id", version, 0, &right), + ); + let (accepted, winning_intent) = match (left_result, right_result) { + (Ok(accepted), Err(PersistenceError::Conflict { .. })) => (accepted, left), + (Err(PersistenceError::Conflict { .. }), Ok(accepted)) => (accepted, right), + results => panic!("expected one accepted image update and one conflict: {results:?}"), + }; + let visible = second + .get_message::("sandbox-id") + .await + .unwrap() + .unwrap(); + assert_eq!(visible, accepted); + assert_eq!(resource_version(&visible), version + 1); + assert_eq!(image(&visible), winning_intent.image); + let pending = second + .get_sandbox_image_state("sandbox-id") + .await + .unwrap() + .unwrap(); + assert_eq!(pending.revision, 1); + assert_eq!(pending.pending, Some(winning_intent.clone())); + let operation = visible + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap(); + assert!(operation.driver_operation_pending); + assert_eq!(operation.driver_operation_id, winning_intent.operation_id); + + let mut stale_start = original; + stale_start.status.as_mut().unwrap().phase = SandboxPhase::Starting as i32; + assert!(matches!( + second + .put_if( + "sandbox", + "sandbox-id", + "sandbox-name", + "default", + &stale_start.encode_to_vec(), + None, + WriteCondition::MatchResourceVersion(version), + ) + .await, + Err(PersistenceError::Conflict { .. }) + )); + + // Model an old gateway's ordinary unrelated Sandbox-only SQL rewrite. + // All provisioning fields used here are baseline-known. The private + // state has no public Sandbox field for this roundtrip to overwrite. + let mut legacy_rewrite = Sandbox::decode(visible.encode_to_vec().as_slice()).unwrap(); + legacy_rewrite + .metadata + .as_mut() + .unwrap() + .annotations + .insert( + "test.openshell.dev/legacy-write".into(), + "unrelated-edit".into(), + ); + let Store::Postgres(legacy_store) = &second else { + unreachable!() + }; + let rewritten = sqlx::query( + "UPDATE objects SET payload = $2, resource_version = resource_version + 1, +updated_at_ms = $3 WHERE object_type = 'sandbox' AND id = $1 AND resource_version = $4", + ) + .bind("sandbox-id") + .bind(legacy_rewrite.encode_to_vec()) + .bind(current_time_ms()) + .bind(i64::try_from(resource_version(&visible)).unwrap()) + .execute(&legacy_store.pool) + .await + .unwrap(); + assert_eq!(rewritten.rows_affected(), 1); + assert_eq!( + first.get_sandbox_image_state("sandbox-id").await.unwrap(), + Some(pending) + ); + let rewritten = first + .get_message::("sandbox-id") + .await + .unwrap() + .unwrap(); + assert_eq!(resource_version(&rewritten), resource_version(&visible) + 1); + let operation = rewritten + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap(); + assert!(operation.driver_operation_pending); + assert_eq!(operation.driver_operation_id, winning_intent.operation_id); + let private_identity = sqlx::query_as::<_, (Option, String)>( + "SELECT name, workspace FROM objects WHERE object_type = $1 AND id = $2", + ) + .bind(IMAGE_STATE_OBJECT_TYPE) + .bind(state_id("sandbox-id")) + .fetch_one(&legacy_store.pool) + .await + .unwrap(); + assert_eq!(private_identity, (None, String::new())); + + let completed = first + .complete_sandbox_image_update("sandbox-id", &winning_intent) + .await + .unwrap(); + assert_eq!( + resource_version(&completed), + resource_version(&rewritten) + 1 + ); + assert!(completed.status.as_ref().unwrap().provisioning.is_none()); + let retained = second + .get_sandbox_image_state("sandbox-id") + .await + .unwrap() + .unwrap(); + assert_eq!(retained.revision, 1); + assert!(retained.pending.is_none()); + assert!(retained.previous_provisioning.is_none()); + + let next = StoredSandboxImageUpdate { + operation_id: "next-generation-operation".into(), + previous_image: winning_intent.image.clone(), + image: "next-generation-image".into(), + revision: 2, + ..Default::default() + }; + let accepted_next = second + .begin_sandbox_image_update("sandbox-id", resource_version(&completed), 1, &next) + .await + .unwrap(); + assert_eq!(image(&accepted_next), next.image); + assert!(matches!( + first + .complete_sandbox_image_update("sandbox-id", &winning_intent) + .await, + Err(PersistenceError::Conflict { .. }) + )); + let still_pending = first + .get_sandbox_image_state("sandbox-id") + .await + .unwrap() + .unwrap(); + assert_eq!(still_pending.revision, 2); + assert_eq!(still_pending.pending, Some(next.clone())); + assert_eq!( + first + .get_message::("sandbox-id") + .await + .unwrap() + .unwrap(), + accepted_next + ); + + let final_sandbox = first + .complete_sandbox_image_update("sandbox-id", &next) + .await + .unwrap(); + assert_eq!( + resource_version(&final_sandbox), + resource_version(&accepted_next) + 1 + ); + assert!( + final_sandbox + .status + .as_ref() + .unwrap() + .provisioning + .is_none() + ); + assert_eq!( + second + .get_message::("sandbox-id") + .await + .unwrap() + .unwrap(), + final_sandbox + ); + let retained = second + .get_sandbox_image_state("sandbox-id") + .await + .unwrap() + .unwrap(); + assert_eq!(retained.revision, 2); + assert!(retained.pending.is_none()); + assert!(retained.previous_provisioning.is_none()); + // Completion also invalidates a Start CAS based on the pending snapshot. + assert!(matches!( + second + .put_if( + "sandbox", + "sandbox-id", + "sandbox-name", + "default", + &accepted_next.encode_to_vec(), + None, + WriteCondition::MatchResourceVersion(resource_version(&accepted_next)), + ) + .await, + Err(PersistenceError::Conflict { .. }) + )); + + first.close().await; + second.close().await; + schema.drop_schema().await; + } +} diff --git a/crates/openshell-server/src/persistence/sqlite.rs b/crates/openshell-server/src/persistence/sqlite.rs index cb76e3fa6f..c8efccc61c 100644 --- a/crates/openshell-server/src/persistence/sqlite.rs +++ b/crates/openshell-server/src/persistence/sqlite.rs @@ -48,7 +48,7 @@ pub const DEFAULT_MAX_CONNECTIONS: u32 = 5; #[derive(Debug, Clone)] pub struct SqliteStore { - pool: SqlitePool, + pub(super) pool: SqlitePool, /// Pool for writes whose loss after a crash is harmless; see /// [`SqliteStore::create_relaxed`]. On-disk stores open it with /// `synchronous=NORMAL`; in-memory stores share `pool`. diff --git a/crates/openshell-server/src/reflection.rs b/crates/openshell-server/src/reflection.rs index 140b53659e..d8eb12482e 100644 --- a/crates/openshell-server/src/reflection.rs +++ b/crates/openshell-server/src/reflection.rs @@ -28,7 +28,7 @@ use openshell_core::Config; use crate::multiplex::GrpcRateLimiter; const REFLECTED_PROTO_ROOTS: &[&str] = &["openshell.proto"]; -const ADVERTISED_SERVICES: &[&str] = &["openshell.v1.OpenShell"]; +const ADVERTISED_SERVICES: &[&str] = &["openshell.v1.OpenShell", "openshell.v1.SandboxUpdates"]; pub type GatewayReflectionServer = ServerReflectionServer; diff --git a/crates/openshell-server/src/storage_proto.rs b/crates/openshell-server/src/storage_proto.rs index 3b902df1f9..72241f0f00 100644 --- a/crates/openshell-server/src/storage_proto.rs +++ b/crates/openshell-server/src/storage_proto.rs @@ -15,6 +15,11 @@ include!(concat!(env!("OUT_DIR"), "/openshell.storage.v1.rs")); +mod sandbox_image { + include!(concat!(env!("OUT_DIR"), "/openshell.storage.image.v1.rs")); +} +pub(crate) use sandbox_image::{StoredSandboxImageState, StoredSandboxImageUpdate}; + pub(crate) const STORAGE_FILE_DESCRIPTOR_SET: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/storage_descriptor.bin")); @@ -156,12 +161,20 @@ mod tests { // Sandbox-wide grant reporting adds TokenGrantFailureObservation to both // closures and TokenGrantStatus to public reports. Older rows have no grant // record; absent report extensions retain compatibility without a rewrite. + // Image updates add a separate public service/request and a new private + // image-state format. Existing public status and frozen storage.v1 stay + // unchanged; only the private durable closure grows. + // Configuration updates add presence-aware argv/map wrappers shared with + // private durable intent. Existing image-only payloads decode with all four + // selections absent; storage.v1 remains frozen. Public field 3 retains its + // image wire identity, now with explicit presence. Compute configuration + // updates use a separate capability/service and add one classified RPC. const PUBLIC_RPC_SCHEMA_SHA256: &str = - "a91e2244a2c1a8e369efe85bd0e4f2769f0f29f6263137280fe38e8a8da3cc6e"; + "91e189c469fc4674efb689112c130ce4c97d9d6bed552a3dbc1c0034d9aaa7d4"; const DURABLE_SCHEMA_SHA256: &str = - "d4dc7abdb91b919db1f7be0d4652e30cbfda86f3b2ed9b59d5de1f76a2bf691d"; + "8a92a73f83f11fee67def7e83b471164a61d6f73cbc4113fc8cfe3873619c5ee"; const PUBLIC_DURABLE_OVERLAP_SHA256: &str = - "ac1ffc4b5b81467de27270a4e33f6db1e31eeb2c52f79750dbaaf19515eadbb2"; + "c0c3ea17f105aaa6d843c466fd4240451c2105dce7675384935d4aaecdac8abf"; // Encoded with the schema that omitted stable_placeholder. Keep these // bytes fixed so the current encoder cannot hide a compatibility change. const LEGACY_STATIC_PROFILE: &str = "0a240a116c65676163792d70726f66696c652d6964120d6c65676163792d737461746963280712510a0d6c65676163792d73746174696312184c6567616379207374617469632063726564656e7469616c2a260a076170695f6b65791a1153594e5448455449435f4150495f4b455920012a06626561726572"; @@ -222,6 +235,7 @@ mod tests { ".openshell.storage.v1.StoredConfigUpdateOperation", ".openshell.storage.v1.StoredProviderCredentialRefreshStateV2", ".openshell.storage.v1.StoredProviderProfile", + ".openshell.storage.image.v1.StoredSandboxImageState", ".openshell.v1.Sandbox", ".openshell.v1.SandboxWorkloadTemplate", ".openshell.v1.ServiceEndpoint", @@ -464,7 +478,12 @@ mod tests { let public = FileDescriptorSet::decode(openshell_core::FILE_DESCRIPTOR_SET) .expect("public descriptor set must decode"); for file in public.file { - assert_ne!(file.package.as_deref(), Some("openshell.storage.v1")); + assert!( + !file + .package + .as_deref() + .is_some_and(|package| package.starts_with("openshell.storage.")) + ); for message in file.message_type { let name = message.name.expect("message name"); assert!( @@ -569,7 +588,8 @@ mod tests { } if !matches!( (package, service_name), - ("openshell.v1", "OpenShell") | ("openshell.inference.v1", "Inference") + ("openshell.v1", "OpenShell" | "SandboxUpdates") + | ("openshell.inference.v1", "Inference") ) { continue; } @@ -624,7 +644,7 @@ mod tests { } assert_eq!( compiled_method_count, - 103 + PROVIDER_READINESS_RPC_SIGNATURES.len() + 106 + PROVIDER_READINESS_RPC_SIGNATURES.len() + PEER_OWNER_RPC_SIGNATURES.len() + PEER_CONFIG_RPC_SIGNATURES.len() + isolation_methods.len() @@ -634,7 +654,7 @@ mod tests { ); assert_eq!( methods.len(), - 78 + PROVIDER_READINESS_RPC_SIGNATURES.len() + 79 + PROVIDER_READINESS_RPC_SIGNATURES.len() + PEER_OWNER_RPC_SIGNATURES.len() + PEER_CONFIG_RPC_SIGNATURES.len(), "inventory every public gateway RPC" @@ -695,9 +715,9 @@ mod tests { overlap_hash.as_str(), ), ( - (328, 29), - (95, 22), - (83, 22), + (332, 29), + (100, 22), + (86, 22), PUBLIC_RPC_SCHEMA_SHA256, DURABLE_SCHEMA_SHA256, PUBLIC_DURABLE_OVERLAP_SHA256 diff --git a/crates/openshell-server/src/test_support.rs b/crates/openshell-server/src/test_support.rs index 5aa4a70f5c..c6e6a88120 100644 --- a/crates/openshell-server/src/test_support.rs +++ b/crates/openshell-server/src/test_support.rs @@ -15,6 +15,8 @@ use crate::tracing_bus::TracingLogBus; use futures::{Stream, stream}; use openshell_core::Config; #[cfg(unix)] +use openshell_core::proto::compute::v1::compute_driver_image_updates_server::ComputeDriverImageUpdatesServer; +#[cfg(unix)] use openshell_core::proto::compute::v1::compute_driver_server::ComputeDriverServer; use openshell_core::proto::compute::v1::{ CreateSandboxRequest, CreateSandboxResponse, DeleteSandboxRequest, DeleteSandboxResponse, @@ -105,6 +107,12 @@ pub enum FakeComputeDriverCall { CreateSandbox { sandbox: Option, }, + UpdateSandboxConfiguration { + request: openshell_core::proto::compute::v1::UpdateSandboxConfigurationRequest, + }, + UpdateSandboxImage { + request: openshell_core::proto::compute::v1::UpdateSandboxImageRequest, + }, StopSandbox { sandbox_id: String, sandbox_name: String, @@ -133,6 +141,8 @@ struct FakeComputeDriverState { sandboxes: HashMap, calls: Vec, traceparents: Vec, + image_update_error: Option, + image_validation_error: Option, } impl Default for FakeComputeDriver { @@ -176,6 +186,8 @@ impl FakeComputeDriver { sandboxes: HashMap::new(), calls: Vec::new(), traceparents: Vec::new(), + image_update_error: None, + image_validation_error: None, })), } } @@ -218,6 +230,20 @@ impl FakeComputeDriver { self } + #[must_use] + pub fn with_sandbox_image_updates(self) -> Self { + self.with_state(|state| { + state + .capabilities + .extension + .as_mut() + .unwrap() + .supported_capabilities + .push(openshell_core::extension_protocol::COMPUTE_UPDATE_SANDBOX_IMAGE.into()); + }); + self + } + #[must_use] pub fn calls(&self) -> Vec { self.with_state(|state| state.calls.clone()) @@ -246,13 +272,23 @@ impl FakeComputeDriver { let driver = self.clone(); let task = tokio::spawn(async move { tonic::transport::Server::builder() - .add_service(ComputeDriverServer::new(driver)) + .add_service(ComputeDriverServer::new(driver.clone())) + .add_service(openshell_core::proto::compute::v1::compute_driver_configuration_updates_server::ComputeDriverConfigurationUpdatesServer::new(driver.clone())) + .add_service(ComputeDriverImageUpdatesServer::new(driver)) .serve_with_incoming(UnixIncoming { listener }) .await }); Ok(FakeComputeDriverServerHandle { socket_path, task }) } + pub fn set_image_update_error(&self, code: Option) { + self.with_state(|state| state.image_update_error = code); + } + + pub fn set_image_validation_error(&self, code: Option) { + self.with_state(|state| state.image_validation_error = code); + } + fn with_state(&self, f: impl FnOnce(&mut FakeComputeDriverState) -> R) -> R { let mut state = self .state @@ -303,6 +339,78 @@ impl Stream for UnixIncoming { } } +#[tonic::async_trait] +impl openshell_core::compute::ComputeDriver for FakeComputeDriver { + async fn update_sandbox_configuration( + &self, + request: Request, + ) -> Result< + Response, + Status, + > { + self.record_traceparent(request.metadata()); + self.with_state(|state| { + let request = request.into_inner(); + let validate_only = request.image_update.as_ref().unwrap().validate_only; + state + .calls + .push(FakeComputeDriverCall::UpdateSandboxConfiguration { request }); + if let Some(code) = state.image_validation_error.filter(|_| validate_only) { + return Err(Status::new(code, "injected configuration validation error")); + } + if let Some(code) = state.image_update_error.filter(|_| !validate_only) { + return Err(Status::new(code, "injected configuration update error")); + } + Ok(Response::new( + openshell_core::proto::compute::v1::UpdateSandboxConfigurationResponse {}, + )) + }) + } + + async fn update_sandbox_image( + &self, + request: Request, + ) -> Result, Status> + { + self.record_traceparent(request.metadata()); + self.with_state(|state| { + let request = request.into_inner(); + let validate_only = request.validate_only; + state + .calls + .push(FakeComputeDriverCall::UpdateSandboxImage { request }); + if let Some(code) = state.image_validation_error.filter(|_| validate_only) { + return Err(Status::new(code, "injected image validation error")); + } + if let Some(code) = state.image_update_error.filter(|_| !validate_only) { + return Err(Status::new(code, "injected image update error")); + } + Ok(Response::new( + openshell_core::proto::compute::v1::UpdateSandboxImageResponse {}, + )) + }) + } +} + +#[tonic::async_trait] +impl openshell_core::proto::compute::v1::compute_driver_image_updates_server::ComputeDriverImageUpdates + for FakeComputeDriver +{ + async fn update_sandbox_image( + &self, + request: Request, + ) -> Result, Status> { + openshell_core::compute::ComputeDriver::update_sandbox_image(self, request).await + } +} + +#[tonic::async_trait] +impl openshell_core::proto::compute::v1::compute_driver_configuration_updates_server::ComputeDriverConfigurationUpdates for FakeComputeDriver { + async fn update_sandbox_configuration(&self, request: Request) -> Result, Status> { + openshell_core::compute::ComputeDriver::update_sandbox_configuration(self, request).await + } +} + #[tonic::async_trait] impl ComputeDriver for FakeComputeDriver { async fn authenticate_sandbox( diff --git a/docs/how-it-works/gateways/configuration.mdx b/docs/how-it-works/gateways/configuration.mdx index 7f16929759..091a136f50 100644 --- a/docs/how-it-works/gateways/configuration.mdx +++ b/docs/how-it-works/gateways/configuration.mdx @@ -163,6 +163,9 @@ metrics_bind_address = "0.0.0.0:9090" log_level = "info" +# Activate only after retiring incompatible gateways and outstanding driver work. +enable_sandbox_updates = false + # Connection ceiling for the gateway's database pool. Omit to use the backend # default: 10 for Postgres, 5 for on-disk SQLite. database_max_connections = 10 @@ -341,6 +344,44 @@ External compute drivers declare this requirement by adding `openshell.compute.l `[openshell.gateway.auth] allow_unauthenticated_users = true` is an unsafe local-development and trusted-proxy escape hatch. It accepts user-facing CLI/API calls without OIDC or mTLS credentials while sandbox supervisors still authenticate with gateway-minted sandbox JWTs. Leave it false for shared and production gateways. +## Sandbox Updates + +`[openshell.gateway] enable_sandbox_updates` defaults to `false`. It gates +the separate `openshell.v1.SandboxUpdates/UpdateSandbox` RPC on the existing +gateway listener. It gates image, command, environment, label, and annotation +updates for stopped sandboxes. Protocol 2 requires upgrading all +gateways, including gateways that previously supported image-only updates. +The equivalent gateway process flag and environment variable +are `--enable-sandbox-updates true` and +`OPENSHELL_ENABLE_SANDBOX_UPDATES=true`. + +Enable it only after completing the +[activation checklist](/kubernetes/high-availability#activate-sandbox-updates). +Setting it to `true` attests that every gateway that can write the database is +compatible and incompatible gateways cannot return. Keep it false throughout a +rolling upgrade, including while outstanding operations from retired gateways +settle. Database and membership checks fail closed but do not prove retirement. + +For a schema-v2 file, set: + +```toml +[openshell.gateway] +enable_sandbox_updates = true +``` + +The canonical Helm values form uses a dotted table key: + +```yaml +gatewayConfig: + "openshell.gateway": + enable_sandbox_updates: true +``` + +This is not an automatic mixed-version migration switch. Do not downgrade while +an image update is pending or retained sandboxes require the private image +revision protocol. SQLite supports one gateway process only; activation attests +that the previous process has stopped, not that sharing a SQLite file enables HA. + ## Database Pool `[openshell.gateway] database_max_connections` sets the connection ceiling for the gateway's database pool. One pool is shared by every database-backed RPC, so this value is also the gateway's ceiling on concurrent database work. When the pool is saturated, callers queue on connection acquisition and sqlx logs that the time to acquire a connection exceeded its slow threshold. The background readiness monitor draws from the same pool, so sustained saturation can push its database ping past the check timeout and flip `/readyz` to not-ready while the gateway is otherwise serving. @@ -1463,7 +1504,7 @@ compute_driver = "kyma" socket_path = "/run/openshell/kyma-compute-driver.sock" ``` -## Preflight package configuration {#gateway-config-preflight} +

Preflight package configuration

Before starting a package-managed gateway, validate the selected file without changing it: diff --git a/docs/how-it-works/sandboxes/overview.mdx b/docs/how-it-works/sandboxes/overview.mdx index 430e90c92f..1f66058c05 100644 --- a/docs/how-it-works/sandboxes/overview.mdx +++ b/docs/how-it-works/sandboxes/overview.mdx @@ -1004,6 +1004,104 @@ If an infrastructure failure leaves a sandbox in `Error`, fix the reported cause For a provisioning timeout, wait for cleanup to finish, then run `sandbox start` directly. +### Update the Image of a Stopped Sandbox + +Use `openshell.v1.SandboxUpdates/UpdateSandbox` to change a stopped sandbox's +workload image, command, environment overrides, labels, and annotations together. This separate service uses the same gateway listener +and authentication as `OpenShell`; it requires `sandbox:write` and workspace +user access. This operation initially supports the Kubernetes compute driver +only; other drivers return `UNIMPLEMENTED` after activation. There is no +`sandbox update` CLI command or curated SDK update method. + +Image updates default to disabled. Ask the operator to complete the +[compatible deployment rollout](/kubernetes/high-availability#activate-sandbox-updates) +before enabling `enable_sandbox_updates` in the +[gateway configuration](/how-it-works/gateways/configuration#sandbox-updates). + +The update preserves the sandbox ID, Kubernetes custom resource, and persistent +volume claim. It does not delete and recreate the sandbox, reseed its existing +workspace from the new image, or change the trusted supervisor and sandbox +runtime images. Omitted fields retain their current values. A supplied command +replaces the argv list without shell expansion; an empty list restores the default +login shell resolved from the workload image. A supplied environment map replaces +sandbox-level overrides; clearing it restores any template-provided defaults. +Labels and annotations replace user metadata on the gateway resource, preserve +system-owned keys, and do not set Kubernetes pod metadata. Attempts to supply +system-owned keys are rejected. At least one update field is required. +Annotation changes accepted through `UpdateConfig` after the sandbox update is +accepted remain in place when the update completes, including after a retry. + +Without a pending update, a request whose target values already match is a no-op. +Use a different image tag or an immutable digest to select new image content. +Configuration updates require the driver's +`openshell.compute.update-sandbox-configuration` capability; image-only clients +can continue using `openshell.compute.update-sandbox-image`. Upgrade every gateway +before enabling updates: older gateways can ignore new request fields. + +Follow this sequence through the CLI and a generated/raw RPC client. + +1. Run `openshell sandbox stop my-sandbox` and wait for `Stopped`. +2. Call `GetSandbox` with the named `workspace_scope` and sandbox `name`. Read + `sandbox.metadata.resource_version` after stop completes. +3. Call `UpdateSandbox` with the same scope and name, the selected update fields, + and `expected_resource_version` set to that current, nonzero + version. The response is a `SandboxResponse`; the sandbox remains stopped. +4. After the update succeeds, run `openshell sandbox start my-sandbox` to launch + the new image. + +With an authenticated Rust SDK `OpenShellClient`, use the separate raw client +and the `current_resource_version` read in step 2: + +```rust +use openshell_sdk::raw::{UpdateSandboxRequest, proto}; + +let mut updates = client.raw_sandbox_updates_fresh().await?; +let response = updates.update_sandbox(UpdateSandboxRequest { + workspace_scope: Some(proto::workspace_selector("default")), + name: "my-sandbox".into(), + image: Some("registry.example.com/agents/my-agent:2.0".into()), + command: Some(proto::SandboxCommandUpdate { + argv: vec!["/usr/local/bin/start-agent".into(), "--new-option".into()], + }), + environment: Some(proto::SandboxMapUpdate { + values: [("AGENT_MODE".into(), "updated".into())].into_iter().collect(), + }), + expected_resource_version: current_resource_version, + request_id: String::new(), + ..Default::default() +}).await?; +``` + +The helper shares the main client's transport and live authentication slot. +Generated clients use the `SandboxUpdates` service, not `OpenShell`. + +If the new image fails to start and provisioning times out, wait until +`provisioning.cleanup_completed_time` is present and +`provisioning.driver_operation_pending` is false. Run `sandbox stop` to move the +reclaimed sandbox from `Error` to `Stopped`, then repeat the update sequence with +a working image. Stop retains the timeout record and storage; it does not stop +compute again. Cleanup that is still pending cannot be bypassed. + +An `ABORTED` version conflict requires another read before you construct a new +conditional request. An update failure or disconnected response can leave a +pending operation, even when `sandbox.spec.template.image` already names the +target image. Start, stop, provisioning retry, and delete reject the pending +operation. +The public `SandboxStatus` shape is unchanged; image intent and revision remain +private. Use the operation-pending error to guide recovery, not a status image +field. Read the current version and retry `UpdateSandbox` with the same selected fields and target values +you originally requested. Do not select another configuration or delete the sandbox to +bypass reconciliation. Only a successful retry clears the pending operation. + +The optional `request_id` uses [durable request admission](/sdk/api-errors#durable-request-admission). +A retry with the same ID must keep the original payload, including its expected +version. An unresolved admission returns `REQUEST_OUTCOME_UNCERTAIN` rather than +executing again. Reconcile against the sandbox state and your original requested +configuration, then submit a new conditional request for the same target with a fresh version +and a new ID, or no ID. +Do not change the expected version under the original ID or blindly switch IDs +after a timeout. + ## Delete Sandboxes Deleting a sandbox stops all processes, releases resources, and purges injected credentials. diff --git a/docs/kubernetes/high-availability.mdx b/docs/kubernetes/high-availability.mdx index 8b4d467b4e..e13fe905d8 100644 --- a/docs/kubernetes/high-availability.mdx +++ b/docs/kubernetes/high-availability.mdx @@ -504,6 +504,51 @@ during the upgrade, the older replicas serialize every mutation across the fleet. Mutations can be slower and can briefly fail under load until the rollout finishes. +### Activate Sandbox Updates + +Sandbox configuration updates default to disabled. Setting +`[openshell.gateway] enable_sandbox_updates = true` is an operator +attestation that the deployment uniformly supports the image-update protocol. +Use this sequence before activating the +[separate update service](/how-it-works/sandboxes/overview#update-the-image-of-a-stopped-sandbox). + +1. Keep the setting false throughout the rolling upgrade. Drain and retire + every incompatible gateway, including unregistered processes and Jobs, and + settle all outstanding driver operations they started. A completed + Kubernetes rollout alone does not establish this. +2. Prevent incompatible gateways from returning. Pin compatible images and + retire old rollback targets, Jobs, and other launch configurations that could + reconnect to the database. +3. Check all PostgreSQL client backends in the same database. The new binary + sets `application_name` to `openshell-image-update-v2` on every data-pool and + lock-pool connection. Any unmarked client backend, including an + administrative session, blocks image updates. Close those sessions or use a + dedicated database. Do not relabel an old process to bypass this check. +4. Confirm live gateway membership records advertise image-update protocol 2. + Missing or incompatible capabilities, malformed records, and truncated + membership reads reject updates. These observations are not durable proof + of fleet compatibility. Expiry or absence of a membership record does not + prove that its process and driver work have stopped. +5. Activate the flag only after completing retirement and preventing old + gateways from returning. See the exact + [TOML, environment, process flag, and Helm forms](/how-it-works/gateways/configuration#sandbox-updates). + +A pending image update atomically sets the existing +`provisioning.driver_operation_pending` and `provisioning.driver_operation_id` +fields alongside private image intent. Baseline start, stop, delete, and +provisioning-retry paths honor this per-sandbox barrier, including after a +legacy public record round-trip. Successful reconciliation preserves current +provisioning diagnostics and clocks, clears the pending flag, and retains the +new operation ID to fence delayed callbacks. Previously absent provisioning +remains absent unless concurrent writes added diagnostics or clocks. This +barrier does not replace the deployment activation checklist. +Image intent and revision are not new public `SandboxStatus` fields. + +Do not downgrade while updates are pending or retained sandboxes require the +private revision protocol. SQLite supports one gateway process only. For a +SQLite gateway, activation attests that the old process has stopped; a shared +SQLite file is not an HA deployment. + ## Next Steps - To expose the gateway through a highly available data path, refer to diff --git a/docs/sdk/api-errors.mdx b/docs/sdk/api-errors.mdx index 23a34c3b96..b782afc4ba 100644 --- a/docs/sdk/api-errors.mdx +++ b/docs/sdk/api-errors.mdx @@ -56,7 +56,7 @@ guarantee. | `ABORTED` | Read fresh state before retrying a conditional operation. | | `UNAVAILABLE`, `RESOURCE_EXHAUSTED` | A transient condition may recover. Apply backoff and any minimum retry delay only to a retry-safe operation. | | `DEADLINE_EXCEEDED`, `CANCELLED` | A mutation's outcome can be unknown. Cancellation does not imply rollback. | -| `UNIMPLEMENTED` | Check gateway and SDK version compatibility. | +| `UNIMPLEMENTED` | Check gateway and SDK version compatibility and whether the compute driver supports the operation. | | `INTERNAL`, `UNKNOWN`, `DATA_LOSS` | Preserve the status and correlation metadata for diagnosis. Do not blindly retry mutations. | ## SDK access @@ -92,19 +92,23 @@ No SDK automatically retries a mutation as a result of decoding these details. ## Durable request admission -Thirty user-callable unary RPCs accept an optional `request_id` in their protobuf requests. +Thirty-one user-callable unary RPCs accept an optional `request_id` in their protobuf requests. | Resource | RPCs | |---|---| | Workspace | `CreateWorkspace`, `DeleteWorkspace` | | Workspace member | `AddWorkspaceMember`, `RemoveWorkspaceMember` | | Sandbox template | `CreateSandboxTemplate`, `DeleteSandboxTemplate` | -| Sandbox | `CreateSandbox`, `DeleteSandbox`, `StartSandbox`, `StopSandbox`, `AttachSandboxProvider`, `DetachSandboxProvider` | +| Sandbox | `CreateSandbox`, `UpdateSandbox`, `DeleteSandbox`, `StartSandbox`, `StopSandbox`, `AttachSandboxProvider`, `DetachSandboxProvider` | | Service | `ExposeService`, `DeleteService` | | Provider | `CreateProvider`, `UpdateProvider`, `DeleteProvider`, `ConfigureProviderRefresh`, `DeleteProviderRefresh`, `RotateProviderCredential` | | Provider profile | `ImportProviderProfiles`, `UpdateProviderProfiles`, `DeleteProviderProfile` | | Policy and config | `UpdateConfig`, `ApproveDraftChunk`, `RejectDraftChunk`, `EditDraftChunk`, `UndoDraftChunk`, `ApproveAllDraftChunks`, `ClearDraftChunks` | +`UpdateSandbox` belongs to the separate `openshell.v1.SandboxUpdates` service +on the same authenticated gateway listener. The other RPCs above remain on +`openshell.v1.OpenShell`. + Use a nonzero, hyphenated UUID of exactly 36 characters. Uppercase and lowercase forms identify the same request. An empty ID preserves the existing behavior without deduplication. This ID is separate from transport correlation metadata. @@ -192,6 +196,37 @@ from this feature. Sandbox principals cannot opt into `UpdateConfig` admission. Check gateway compatibility before relying on IDs; older protobuf servers can silently ignore unknown fields. +### Sandbox image update recovery + +`SandboxUpdates.UpdateSandbox` conditionally updates image, command, environment overrides, labels, and annotations for stopped +Kubernetes sandboxes. It defaults to disabled and returns `FAILED_PRECONDITION` +until the operator [activates a compatible deployment](/kubernetes/high-availability#activate-sandbox-updates). +After activation, other compute drivers return `UNIMPLEMENTED`. Supply a named +`workspace_scope`, sandbox `name`, at least one selected update field, and a required nonzero +`expected_resource_version` read from `sandbox.metadata.resource_version`. +A stale version returns `ABORTED`; read fresh state before constructing another +conditional request. The operation does not start compute. + +A failed or interrupted update can leave a pending operation. The desired spec +image alone does not prove driver reconciliation completed. Start, stop, +provisioning retry, and delete return `FAILED_PRECONDITION` while that operation remains. +Image intent and revision are private; the public `SandboxStatus` shape does not +change. On an operation-pending error, read a fresh resource version and retry +the same selected fields and target values originally requested. A different configuration cannot replace a +pending update. + +If the original request supplied `request_id`, an error leaves its admission +unresolved. Reusing that ID with the original payload returns +`REQUEST_OUTCOME_UNCERTAIN`; changing its expected version returns +`REQUEST_ID_PAYLOAD_MISMATCH`. Reconcile against the original requested configuration, +then make a new conditional request for the same target with the fresh version and a new +ID, or without an ID. This is explicit recovery, not permission to blindly +repeat uncertain mutations. A replay of a confirmed success returns the +sandbox's current state without applying the image again. + +See [Update the Image of a Stopped Sandbox](/how-it-works/sandboxes/overview#update-the-image-of-a-stopped-sandbox) +for the stop, update, and start workflow and storage guarantees. + ## Exec launch admission `ExecSandbox` and `ExecSandboxInteractive` accept an optional `request_id` in diff --git a/e2e/rust/Cargo.lock b/e2e/rust/Cargo.lock index d9a6e99d03..2d75913dca 100644 --- a/e2e/rust/Cargo.lock +++ b/e2e/rust/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ahash" version = "0.8.12" @@ -30,6 +45,15 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.104" @@ -48,7 +72,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 2.0.19", "time", ] @@ -219,6 +243,30 @@ dependencies = [ "tokio", ] +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + [[package]] name = "base64" version = "0.22.1" @@ -291,7 +339,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_urlencoded", - "thiserror", + "thiserror 2.0.19", "tokio", "tokio-util", "tower-service", @@ -369,8 +417,12 @@ version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ + "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", + "windows-link", ] [[package]] @@ -690,6 +742,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flume" version = "0.12.0" @@ -742,6 +800,7 @@ checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -849,8 +908,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -877,6 +938,18 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + [[package]] name = "gloo-timers" version = "0.3.0" @@ -1164,13 +1237,16 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -1193,11 +1269,35 @@ dependencies = [ "tower-service", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", @@ -1209,22 +1309,43 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", + "serde", "tinystr", "writeable", "zerovec", ] +[[package]] +name = "icu_locale_fallback" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "251af8e57c9400e3eb58242fe5b8b1152b2a64fdf4cf632f923c38ccee6f2fa9" +dependencies = [ + "icu_locale_core", + "icu_locale_fallback_data", + "icu_provider", + "potential_utf", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locale_fallback_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decf2a22ec8fa68f1a0c1129a3f8583f8f8bc24e8b9ccbe98ead99f62a4dc3a8" + [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1236,16 +1357,17 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -1256,18 +1378,20 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", + "serde", + "stable_deref_trait", "writeable", "yoke", "zerofrom", @@ -1275,6 +1399,28 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_segmenter" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d07aafccd67af15d02512a6adf5896fbc5ed00f2e99b471d2efa14016db3db" +dependencies = [ + "icu_collections", + "icu_locale_fallback", + "icu_provider", + "icu_segmenter_data", + "potential_utf", + "smallvec", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_segmenter_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad" + [[package]] name = "ident_case" version = "1.0.1" @@ -1314,6 +1460,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + [[package]] name = "itertools" version = "0.14.0" @@ -1341,9 +1499,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" dependencies = [ "cfg-if", "futures-util", @@ -1359,7 +1517,7 @@ dependencies = [ "jsonptr", "serde", "serde_json", - "thiserror", + "thiserror 2.0.19", ] [[package]] @@ -1372,7 +1530,7 @@ dependencies = [ "pest_derive", "regex", "serde_json", - "thiserror", + "thiserror 2.0.19", ] [[package]] @@ -1458,7 +1616,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "thiserror", + "thiserror 2.0.19", "tokio", "tokio-util", "tower", @@ -1481,7 +1639,7 @@ dependencies = [ "serde", "serde-value", "serde_json", - "thiserror", + "thiserror 2.0.19", ] [[package]] @@ -1520,7 +1678,7 @@ dependencies = [ "pin-project", "serde", "serde_json", - "thiserror", + "thiserror 2.0.19", "tokio", "tokio-util", "tracing", @@ -1604,6 +1762,36 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "mime" version = "0.3.17" @@ -1616,6 +1804,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.2.2" @@ -1627,6 +1824,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "nix" version = "0.29.0" @@ -1698,6 +1901,35 @@ dependencies = [ "autocfg", ] +[[package]] +name = "oauth2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" +dependencies = [ + "base64 0.22.1", + "chrono", + "getrandom 0.2.17", + "http", + "rand 0.8.8", + "reqwest", + "serde", + "serde_json", + "serde_path_to_error", + "sha2 0.10.9", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "oid-registry" version = "0.8.1" @@ -1713,6 +1945,58 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "openshell-bootstrap" +version = "0.0.0" +dependencies = [ + "bytes", + "miette", + "openshell-core", + "openshell-crypto", + "rcgen", + "serde", + "serde_json", + "tempfile", + "tokio", + "tracing", +] + +[[package]] +name = "openshell-core" +version = "0.0.0" +dependencies = [ + "async-trait", + "base64 0.22.1", + "chrono", + "glob", + "ipnet", + "miette", + "nix", + "openshell-crypto", + "openshell-extension-core", + "openshell-policy-schema", + "prost", + "prost-types", + "protoc-bin-vendored", + "reqwest", + "rustix", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "sha2 0.10.9", + "thiserror 2.0.19", + "tokio", + "tokio-stream", + "tonic", + "tonic-prost", + "tonic-prost-build", + "tonic-types", + "tracing", + "url", + "uuid", +] + [[package]] name = "openshell-crypto" version = "0.0.0" @@ -1725,7 +2009,7 @@ dependencies = [ "rustls", "serde", "sqlx", - "thiserror", + "thiserror 2.0.19", "tonic", "x509-parser", ] @@ -1745,7 +2029,9 @@ dependencies = [ "jsonwebtoken", "nix", "noyalib", + "openshell-bootstrap", "openshell-crypto", + "openshell-sdk", "prost", "rand 0.9.5", "rustls", @@ -1765,6 +2051,55 @@ dependencies = [ "url", ] +[[package]] +name = "openshell-extension-core" +version = "0.0.0" +dependencies = [ + "hyper-util", + "serde", + "thiserror 2.0.19", + "tokio", + "tonic", + "tower", +] + +[[package]] +name = "openshell-policy-schema" +version = "0.0.0" +dependencies = [ + "miette", + "noyalib", + "serde", + "serde_json", + "serde_path_to_error", + "thiserror 2.0.19", +] + +[[package]] +name = "openshell-sdk" +version = "0.0.0" +dependencies = [ + "async-stream", + "async-trait", + "futures", + "hyper", + "hyper-util", + "miette", + "oauth2", + "openshell-core", + "openshell-crypto", + "reqwest", + "rustls", + "serde", + "thiserror 2.0.19", + "tokio", + "tokio-rustls", + "tokio-tungstenite", + "tonic", + "tower", + "tracing", +] + [[package]] name = "openssl-probe" version = "0.2.1" @@ -1780,6 +2115,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "owo-colors" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8" + [[package]] name = "parking" version = "2.2.1" @@ -1877,6 +2218,17 @@ dependencies = [ "pest", ] +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", +] + [[package]] name = "pin-project" version = "1.1.13" @@ -1915,6 +2267,8 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ + "serde_core", + "writeable", "zerovec", ] @@ -1933,6 +2287,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -1952,6 +2316,27 @@ dependencies = [ "prost-derive", ] +[[package]] +name = "prost-build" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "pulldown-cmark", + "pulldown-cmark-to-cmark", + "regex", + "syn 2.0.119", + "tempfile", +] + [[package]] name = "prost-derive" version = "0.14.4" @@ -1965,6 +2350,99 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "prost-types" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" +dependencies = [ + "prost", +] + +[[package]] +name = "protoc-bin-vendored" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8760a25b6ff9c620324822737e468478fa092234190d2e449760344354896ed9" +dependencies = [ + "protoc-bin-vendored-linux-aarch_64", + "protoc-bin-vendored-linux-ppcle_64", + "protoc-bin-vendored-linux-s390_64", + "protoc-bin-vendored-linux-x86_32", + "protoc-bin-vendored-linux-x86_64", + "protoc-bin-vendored-macos-aarch_64", + "protoc-bin-vendored-macos-x86_64", + "protoc-bin-vendored-win32", +] + +[[package]] +name = "protoc-bin-vendored-linux-aarch_64" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fa2624782ca04cd44f51554566717377acd240e4c0016d757dd74fccc9324f" + +[[package]] +name = "protoc-bin-vendored-linux-ppcle_64" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2417e9817fa237dab803ad4dda7357a111656e242959cc6b8f9a1a583367d42" + +[[package]] +name = "protoc-bin-vendored-linux-s390_64" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d189c34636356a46a7ed3188233dc8a88c431278cc54d4a19b096a2d270e985" + +[[package]] +name = "protoc-bin-vendored-linux-x86_32" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171e39f1e846e5f322ced1ac3b8d4cd3a3833ca24b6e5d58b3632574fe6204fa" + +[[package]] +name = "protoc-bin-vendored-linux-x86_64" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873cdcc097593432086661aa432b8078f1cd87bfb02847c332e98ae2c119e966" + +[[package]] +name = "protoc-bin-vendored-macos-aarch_64" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb72df001783b8297847fe8f5f874ee400fd742c843d60583e8c23d96977c7f" + +[[package]] +name = "protoc-bin-vendored-macos-x86_64" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b04652167eca899dda05f32f5481adeaf25c623a98ce2fc146a001cc59a2add7" + +[[package]] +name = "protoc-bin-vendored-win32" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "263a3f48f01e7309e857138bd47f785585b4a005e8e56c6d2824ce91195999c3" + +[[package]] +name = "pulldown-cmark" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "22.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84bbb29c624230c4bd1047bbdb2aa47e41c860e9665ce62ba9504eebe91bf867" +dependencies = [ + "pulldown-cmark", +] + [[package]] name = "quote" version = "1.0.47" @@ -1986,13 +2464,24 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "rand_chacha", + "rand_chacha 0.9.0", "rand_core 0.9.5", ] @@ -2007,6 +2496,16 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_chacha" version = "0.9.0" @@ -2093,6 +2592,45 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "ring" version = "0.17.14" @@ -2107,6 +2645,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + [[package]] name = "rustc-hash" version = "2.1.3" @@ -2339,6 +2883,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_repr" version = "0.1.21" @@ -2477,7 +3032,7 @@ checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 2.0.19", "time", ] @@ -2556,7 +3111,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror", + "thiserror 2.0.19", "tokio", "tokio-stream", "tracing", @@ -2597,7 +3152,7 @@ dependencies = [ "sqlx-postgres", "sqlx-sqlite", "syn 2.0.119", - "thiserror", + "thiserror 2.0.19", "tokio", "url", ] @@ -2624,7 +3179,7 @@ dependencies = [ "sha1 0.11.0", "sha2 0.11.0", "sqlx-core", - "thiserror", + "thiserror 2.0.19", "tracing", ] @@ -2658,7 +3213,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.19", "tracing", "whoami", ] @@ -2682,7 +3237,7 @@ dependencies = [ "percent-encoding", "serde", "sqlx-core", - "thiserror", + "thiserror 2.0.19", "tracing", "url", ] @@ -2716,6 +3271,27 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + [[package]] name = "syn" version = "2.0.119" @@ -2743,6 +3319,9 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -2768,13 +3347,53 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "textwrap" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ecfad6c3abc80a577f2b91c1e412ee57e7a060d430b553c1b0c940974ebcd49" +dependencies = [ + "icu_segmenter", + "unicode-width 0.2.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.19", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -2820,11 +3439,12 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", + "serde_core", "zerovec", ] @@ -2883,6 +3503,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.19" @@ -2917,6 +3553,7 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", + "rustls-native-certs", "socket2", "sync_wrapper", "tokio", @@ -2928,6 +3565,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic-build" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f61875ac5293cf72e6c8cf0158086428c82c37229e98c840878f1706b0322" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "tonic-prost" version = "0.14.6" @@ -2939,6 +3588,33 @@ dependencies = [ "tonic", ] +[[package]] +name = "tonic-prost-build" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "654e5643eff75d7f8c99197ce1440ed19a3474eada74c12bbac488b2cafdae27" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn 2.0.119", + "tempfile", + "tonic-build", +] + +[[package]] +name = "tonic-types" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab1b02061f83d519bba3caa167f88f261ef05720ab8ebc954ade70de3348e8" +dependencies = [ + "prost", + "prost-types", + "tonic", +] + [[package]] name = "tower" version = "0.5.3" @@ -2967,13 +3643,16 @@ dependencies = [ "base64 0.22.1", "bitflags", "bytes", + "futures-util", "http", "http-body", "mime", "pin-project-lite", + "tower", "tower-layer", "tower-service", "tracing", + "url", ] [[package]] @@ -3026,6 +3705,25 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.5", + "rustls", + "rustls-pki-types", + "sha1 0.10.7", + "thiserror 2.0.19", + "utf-8", +] + [[package]] name = "typenum" version = "1.20.1" @@ -3038,6 +3736,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "unicase" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357cc3acc6a036009fd6c973ed009037c732d60d0b4f6c673e9041497482a28f" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -3065,6 +3769,18 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -3093,14 +3809,32 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -3139,9 +3873,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" dependencies = [ "cfg-if", "once_cell", @@ -3150,11 +3884,21 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3162,26 +3906,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "1.0.10" @@ -3219,12 +3973,65 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3315,9 +4122,9 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "x509-parser" @@ -3333,7 +4140,7 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror", + "thiserror 2.0.19", "time", ] @@ -3433,21 +4240,23 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", "zerofrom", + "zerovec", ] [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ + "serde", "yoke", "zerofrom", "zerovec-derive", @@ -3455,13 +4264,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] diff --git a/e2e/rust/Cargo.toml b/e2e/rust/Cargo.toml index 1dc6f0b1cf..179568fb9f 100644 --- a/e2e/rust/Cargo.toml +++ b/e2e/rust/Cargo.toml @@ -31,7 +31,7 @@ e2e-docker = ["e2e", "e2e-host-gateway", "e2e-local-container-driver"] e2e-config-push = ["e2e-docker"] e2e-gpu = ["e2e"] e2e-docker-gpu = ["e2e-docker", "e2e-gpu"] -e2e-kubernetes = ["e2e"] +e2e-kubernetes = ["e2e", "dep:openshell-bootstrap", "dep:openshell-sdk"] e2e-kubernetes-ha = ["e2e-kubernetes"] e2e-kubernetes-credential-drivers = ["e2e-kubernetes"] e2e-kubernetes-workspace-managed = ["e2e-kubernetes"] @@ -178,6 +178,11 @@ name = "kubernetes_runtime_reconcile" path = "tests/kubernetes_runtime_reconcile.rs" required-features = ["e2e-kubernetes"] +[[test]] +name = "kubernetes_image_update" +path = "tests/kubernetes_image_update.rs" +required-features = ["e2e-kubernetes"] + [[test]] name = "credential_drivers" path = "tests/credential_drivers.rs" @@ -250,6 +255,8 @@ required-features = ["e2e-gpu"] [dependencies] openshell-crypto = { path = "../../crates/openshell-crypto" } +openshell-bootstrap = { path = "../../crates/openshell-bootstrap", optional = true } +openshell-sdk = { path = "../../crates/openshell-sdk", optional = true } base64 = "0.22" bollard = "0.20" bytes = "1" diff --git a/e2e/rust/tests/kubernetes_image_update.rs b/e2e/rust/tests/kubernetes_image_update.rs new file mode 100644 index 0000000000..d1ff02e71f --- /dev/null +++ b/e2e/rust/tests/kubernetes_image_update.rs @@ -0,0 +1,478 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +#![cfg(feature = "e2e-kubernetes")] + +//! Exercise the public image-update RPC in both Agent Sandbox API-version lanes. +//! Kubernetes object checks are read-only and scoped to this test's sandbox. + +use std::collections::BTreeMap; +use std::path::PathBuf; +use std::time::Duration; + +use openshell_bootstrap::{load_active_gateway, load_gateway_metadata}; +use openshell_e2e::harness::cli::{run_cli, wait_for_sandbox_phase}; +use openshell_e2e::harness::sandbox::SandboxGuard; +use openshell_sdk::raw::{AuthedGrpcClient, AuthedSandboxUpdatesClient, proto}; +use openshell_sdk::{ClientConfig, EdgeAuthInterceptor, OpenShellClient}; +use serde_json::Value; +use tonic::transport::{Certificate, ClientTlsConfig, Endpoint, Identity}; + +// Multi-platform release manifests; no test-local tags or cluster image loads. +const ORIGINAL_IMAGE: &str = "docker.io/library/alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507"; +const UPDATED_IMAGE: &str = "docker.io/library/alpine:3.22@sha256:5291449c3df73caf6ed85e649dec1b9e818b39a5d8c871e97afc13e9cd5e8fa8"; +const SENTINEL_PATH: &str = "/sandbox/.image-update-sentinel"; + +/// Reuse the wrapper's registered endpoint (including its isolated XDG config). +/// The SDK owns plaintext transport; the `OpenShift` wrapper registers an mTLS +/// gateway, for which we attach its existing client bundle to a tonic channel. +async fn gateway_client() -> OpenShellClient { + let name = std::env::var("OPENSHELL_GATEWAY") + .ok() + .filter(|name| !name.trim().is_empty()) + .or_else(load_active_gateway) + .expect("Kubernetes E2E wrapper must register a gateway"); + let metadata = load_gateway_metadata(&name).expect("load E2E gateway metadata"); + if metadata.gateway_endpoint.starts_with("http://") { + OpenShellClient::connect(ClientConfig::new(metadata.gateway_endpoint)) + .await + .expect("connect to E2E gateway") + } else { + assert!( + matches!(metadata.auth_mode.as_deref(), None | Some("mtls")), + "Kubernetes E2E wrapper must register plaintext or mTLS transport" + ); + let config = std::env::var_os("XDG_CONFIG_HOME").map_or_else( + || PathBuf::from(std::env::var_os("HOME").expect("HOME")).join(".config"), + PathBuf::from, + ); + let bundle = config.join("openshell/gateways").join(&name).join("mtls"); + let tls = ClientTlsConfig::new() + .ca_certificate(Certificate::from_pem( + std::fs::read(bundle.join("ca.crt")).expect("read E2E gateway CA"), + )) + .identity(Identity::from_pem( + std::fs::read(bundle.join("tls.crt")).expect("read E2E client certificate"), + std::fs::read(bundle.join("tls.key")).expect("read E2E client key"), + )); + let channel = Endpoint::from_shared(metadata.gateway_endpoint) + .expect("E2E gateway endpoint") + .connect_timeout(Duration::from_secs(10)) + .timeout(Duration::from_secs(60)) + .tls_config(tls) + .expect("configure E2E gateway mTLS") + .connect() + .await + .expect("connect to E2E gateway over mTLS"); + OpenShellClient::from_parts(channel, EdgeAuthInterceptor::noop()) + } +} + +fn request_id() -> String { + let mut bytes = rand::random::<[u8; 16]>(); + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + let hex = hex::encode(bytes); + format!( + "{}-{}-{}-{}-{}", + &hex[..8], + &hex[8..12], + &hex[12..16], + &hex[16..20], + &hex[20..] + ) +} + +fn rpc(body: T) -> tonic::Request { + let mut request = tonic::Request::new(body); + request.set_timeout(Duration::from_secs(60)); + request +} + +async fn get_sandbox(client: &mut AuthedGrpcClient, name: &str) -> proto::Sandbox { + client + .get_sandbox(rpc(proto::GetSandboxRequest { + workspace_scope: Some(proto::workspace_selector("default")), + name: name.to_owned(), + })) + .await + .expect("get test sandbox") + .into_inner() + .sandbox + .expect("Sandbox response") +} + +async fn kubectl_get(resource: &str, selector: &[&str]) -> Value { + let namespace = std::env::var("OPENSHELL_E2E_SANDBOX_NAMESPACE") + .expect("Kubernetes E2E wrapper must provide the sandbox namespace"); + let context = std::env::var("OPENSHELL_E2E_KUBE_CONTEXT_ACTIVE") + .expect("Kubernetes E2E wrapper must provide its active context"); + assert!(!namespace.trim().is_empty() && !context.trim().is_empty()); + let output = tokio::process::Command::new("kubectl") + .args([ + "--context", + &context, + "--namespace", + &namespace, + "--request-timeout=30s", + "get", + resource, + ]) + .args(selector) + .args(["--output=json"]) + .kill_on_drop(true) + .output() + .await + .expect("run scoped kubectl get"); + assert!( + output.status.success(), + "kubectl get {resource} {selector:?}: {}", + String::from_utf8_lossy(&output.stderr) + ); + serde_json::from_slice(&output.stdout).expect("parse kubectl JSON") +} + +async fn sandbox_resource(id: &str) -> Value { + let selector = format!("openshell.ai/sandbox-id={id}"); + let list = kubectl_get("sandboxes.agents.x-k8s.io", &["--selector", &selector]).await; + let [resource] = list["items"].as_array().expect("Sandbox items").as_slice() else { + panic!("expected exactly one Sandbox resource for {id}: {list}"); + }; + assert_eq!( + resource["metadata"]["labels"]["openshell.ai/sandbox-id"], + id + ); + assert!( + resource["metadata"]["uid"] + .as_str() + .is_some_and(|uid| !uid.is_empty()) + ); + resource.clone() +} + +/// Read claims by their exact names from the actual workload Pod. Do not rely +/// on controller-version-dependent PVC names or labels, or inspect other PVCs. +async fn claim_uids(resource: &Value) -> BTreeMap { + let pod_name = resource["metadata"]["annotations"]["agents.x-k8s.io/pod-name"] + .as_str() + .unwrap_or_else(|| resource["metadata"]["name"].as_str().expect("Sandbox name")); + let pod = kubectl_get("pod", &[pod_name]).await; + assert!( + pod["metadata"]["ownerReferences"] + .as_array() + .expect("workload Pod owners") + .iter() + .any(|owner| owner["uid"] == resource["metadata"]["uid"]), + "workload Pod must belong to this Sandbox resource" + ); + let agent = pod["spec"]["containers"] + .as_array() + .expect("Pod containers") + .iter() + .find(|container| container["name"] == "agent") + .expect("agent container"); + let workspace_volume = agent["volumeMounts"] + .as_array() + .expect("agent volume mounts") + .iter() + .find(|mount| mount["mountPath"] == "/sandbox") + .expect("workspace volume mount"); + let volumes = pod["spec"]["volumes"].as_array().expect("Pod volumes"); + assert!( + volumes.iter().any(|volume| { + volume["name"] == workspace_volume["name"] + && volume["persistentVolumeClaim"]["claimName"] + .as_str() + .is_some() + }), + "workspace must be backed by a PVC" + ); + let mut claims = BTreeMap::new(); + for volume in volumes { + let Some(name) = volume["persistentVolumeClaim"]["claimName"].as_str() else { + continue; + }; + let claim = kubectl_get("persistentvolumeclaim", &[name]).await; + let uid = claim["metadata"]["uid"] + .as_str() + .filter(|uid| !uid.is_empty()) + .expect("PVC UID"); + assert_eq!(claim["status"]["phase"], "Bound"); + claims.insert(name.to_owned(), uid.to_owned()); + } + assert!( + !claims.is_empty(), + "test must exercise PVC-backed workspace storage" + ); + claims +} + +fn assert_resource_retained(before: &Value, after: &Value, image: &str) { + assert_eq!(after["metadata"]["name"], before["metadata"]["name"]); + assert_eq!(after["metadata"]["uid"], before["metadata"]["uid"]); + assert_eq!( + after["spec"]["volumeClaimTemplates"], + before["spec"]["volumeClaimTemplates"] + ); + let agent = after["spec"]["podTemplate"]["spec"]["containers"] + .as_array() + .expect("workload containers") + .iter() + .find(|container| container["name"] == "agent") + .expect("agent container"); + assert_eq!(agent["image"], image); +} + +fn assert_identity_retained(before: &proto::Sandbox, after: &proto::Sandbox) { + assert_eq!( + after.metadata.as_ref().expect("metadata").id, + before.metadata.as_ref().expect("metadata").id + ); + assert_eq!(after.host_key_fingerprint, before.host_key_fingerprint); +} + +async fn assert_base_release(sandbox: &SandboxGuard, release: &str) { + assert!( + sandbox + .exec(&["cat", "/etc/alpine-release"]) + .await + .expect("base image release") + .trim() + .starts_with(release) + ); +} + +fn assert_update_completed(original: &proto::Sandbox, updated: &proto::Sandbox) { + assert_identity_retained(original, updated); + let status = updated.status.as_ref().expect("updated status"); + assert_eq!(status.phase, proto::SandboxPhase::Stopped as i32); + assert!( + status + .provisioning + .as_ref() + .is_none_or(|record| !record.driver_operation_pending), + "successful image update must settle the old-understood operation interlock" + ); + assert_eq!( + updated + .spec + .as_ref() + .expect("spec") + .template + .as_ref() + .expect("template") + .image, + UPDATED_IMAGE + ); +} + +async fn assert_replay( + client: &mut AuthedSandboxUpdatesClient, + update: proto::UpdateSandboxRequest, + updated: &proto::Sandbox, +) { + // Replay restores current resource state, not a captured transient status. + // Use retained driver evidence to prove that no new image operation ran. + let id = &updated.metadata.as_ref().expect("updated metadata").id; + let before = sandbox_resource(id).await; + let evidence = before["metadata"]["annotations"]["openshell.ai/sandbox-image-update"].clone(); + let operation: Value = + serde_json::from_str(evidence.as_str().expect("image operation evidence")) + .expect("parse image operation evidence"); + assert!( + operation["revision"] + .as_u64() + .is_some_and(|revision| revision > 0) + ); + assert!( + operation["operation_id"] + .as_str() + .is_some_and(|id| !id.is_empty()) + ); + let replay = client + .update_sandbox(rpc(update)) + .await + .expect("request-ID replay"); + assert_eq!( + replay + .metadata() + .get("openshell-replayed") + .expect("replay metadata"), + "true" + ); + let replayed = replay.into_inner().sandbox.expect("replayed Sandbox"); + assert_update_completed(updated, &replayed); + assert_eq!(replayed.spec, updated.spec); + let after = sandbox_resource(id).await; + assert_eq!(after["metadata"]["uid"], before["metadata"]["uid"]); + assert_eq!( + after["metadata"]["annotations"]["openshell.ai/sandbox-image-update"], evidence, + "replay must retain the exact driver operation identity and revision" + ); + assert!( + replayed + .metadata + .as_ref() + .expect("replayed metadata") + .resource_version + >= updated + .metadata + .as_ref() + .expect("updated metadata") + .resource_version + ); +} + +async fn assert_image_update_supported(client: &mut AuthedGrpcClient) { + // This test is Kubernetes-only: missing support is a regression, not a skip. + let info = client + .get_gateway_info(rpc(proto::GetGatewayInfoRequest {})) + .await + .expect("get gateway capabilities") + .into_inner(); + assert!(info.extensions.iter().any(|extension| { + extension + .supported_capabilities + .iter() + .any(|capability| capability == "openshell.compute.update-sandbox-configuration") + })); +} + +#[tokio::test] +async fn image_update_retains_workspace_and_identity() { + let gateway = gateway_client().await; + let mut client = gateway.raw_grpc(); + let mut image_client = gateway.raw_sandbox_updates(); + assert_image_update_supported(&mut client).await; + + let mut sandbox = SandboxGuard::create(&["--from", ORIGINAL_IMAGE, "--no-auto-providers"]) + .await + .expect("create original Alpine sandbox"); + let original = get_sandbox(&mut client, &sandbox.name).await; + let id = &original.metadata.as_ref().expect("sandbox metadata").id; + assert!(!id.is_empty(), "sandbox must have a canonical ID"); + assert!( + original.host_key_fingerprint.starts_with("SHA256:"), + "sandbox must have a gateway-managed SSH identity" + ); + assert_base_release(&sandbox, "3.21.").await; + let sentinel = request_id(); + sandbox + .exec(&[ + "sh", + "-c", + &format!("printf '%s' '{sentinel}' > '{SENTINEL_PATH}'"), + ]) + .await + .expect("write workspace sentinel"); + let before = sandbox_resource(id).await; + let original_claims = claim_uids(&before).await; + + let running_update = proto::UpdateSandboxRequest { + workspace_scope: Some(proto::workspace_selector("default")), + name: sandbox.name.clone(), + image: Some(UPDATED_IMAGE.to_owned()), + expected_resource_version: original + .metadata + .as_ref() + .expect("metadata") + .resource_version, + request_id: String::new(), + ..Default::default() + }; + let error = image_client + .update_sandbox(rpc(running_update.clone())) + .await + .expect_err("running update must be rejected"); + assert_eq!(error.code(), tonic::Code::FailedPrecondition); + assert_resource_retained(&before, &sandbox_resource(id).await, ORIGINAL_IMAGE); + + let (output, code) = run_cli(&["sandbox", "stop", &sandbox.name]).await; + assert_eq!(code, 0, "sandbox stop: {output}"); + wait_for_sandbox_phase(&sandbox.name, "Stopped", Duration::from_secs(120)) + .await + .expect("sandbox stopped"); + let stopped = get_sandbox(&mut client, &sandbox.name).await; + let update = proto::UpdateSandboxRequest { + expected_resource_version: stopped + .metadata + .as_ref() + .expect("metadata") + .resource_version, + request_id: request_id(), + command: Some(proto::SandboxCommandUpdate { argv: vec![ + "/bin/sh".into(), "-c".into(), + "mkdir -p /sandbox/harness; printf '%s:%s' \"$AGENT_MODE\" \"$1\" > /sandbox/harness/configuration; exec sleep 86400".into(), + "agent-bootstrap".into(), "new-argument".into(), + ] }), + environment: Some(proto::SandboxMapUpdate { values: std::collections::HashMap::from([("AGENT_MODE".into(), "updated".into())]) }), + labels: Some(proto::SandboxMapUpdate { values: std::collections::HashMap::from([("team".into(), "updated-agent".into())]) }), + annotations: Some(proto::SandboxMapUpdate { values: std::collections::HashMap::from([("example.com/revision".into(), "2".into())]) }), + ..running_update + }; + let updated = image_client + .update_sandbox(rpc(update.clone())) + .await + .expect("update stopped sandbox image") + .into_inner() + .sandbox + .expect("updated Sandbox"); + assert_update_completed(&original, &updated); + assert_resource_retained(&before, &sandbox_resource(id).await, UPDATED_IMAGE); + + assert_replay(&mut image_client, update, &updated).await; + + let (output, code) = run_cli(&["sandbox", "start", &sandbox.name]).await; + assert_eq!(code, 0, "sandbox start: {output}"); + wait_for_sandbox_phase(&sandbox.name, "Ready", Duration::from_secs(180)) + .await + .expect("updated sandbox ready"); + assert_base_release(&sandbox, "3.22.").await; + assert_eq!( + sandbox + .exec(&["cat", "/sandbox/harness/configuration"]) + .await + .expect("updated command and environment ran on start") + .trim(), + "updated:new-argument" + ); + let observed = get_sandbox(&mut client, &sandbox.name).await; + assert_eq!( + observed + .metadata + .as_ref() + .unwrap() + .labels + .get("team") + .map(String::as_str), + Some("updated-agent") + ); + assert_eq!( + observed + .metadata + .as_ref() + .unwrap() + .annotations + .get("example.com/revision") + .map(String::as_str), + Some("2") + ); + assert_eq!( + sandbox + .exec(&["cat", SENTINEL_PATH]) + .await + .expect("retained workspace sentinel") + .trim(), + sentinel + ); + let current = get_sandbox(&mut client, &sandbox.name).await; + assert_identity_retained(&original, ¤t); + let after = sandbox_resource(id).await; + assert_resource_retained(&before, &after, UPDATED_IMAGE); + assert_eq!( + claim_uids(&after).await, + original_claims, + "all mounted PVC names and UIDs must survive image replacement" + ); + sandbox.cleanup().await; +} diff --git a/e2e/with-kube-gateway.sh b/e2e/with-kube-gateway.sh index b13dbc380b..c3d04e123b 100755 --- a/e2e/with-kube-gateway.sh +++ b/e2e/with-kube-gateway.sh @@ -1225,6 +1225,12 @@ helm_extra_args=() helm_post_renderer_args=() helm_extra_args+=(--set "server.telemetryEnabled=${OPENSHELL_TELEMETRY_ENABLED}") helm_extra_args+=(--set supervisor.sandboxRuntime.networkPolicyEnforced=true) +# This wrapper installs an isolated, uniformly built test gateway, not a +# production rolling upgrade. Explicitly activate the image-update scenario; +# callers can keep activation disabled for negative coverage. +if [ "${OPENSHELL_E2E_KUBE_IMAGE_UPDATES:-1}" = "1" ]; then + helm_extra_args+=(--set 'gatewayConfig.openshell\.gateway.enable_sandbox_updates=true') +fi if [ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = "1" ]; then if [ "${OPENSHELL_E2E_KUBE_BUILD_IMAGES}" != "1" ]; then echo "ERROR: external Kubernetes driver e2e requires OPENSHELL_E2E_KUBE_BUILD_IMAGES=1." >&2 diff --git a/proto/compute_driver.proto b/proto/compute_driver.proto index 2d3f67b5d7..5099d78984 100644 --- a/proto/compute_driver.proto +++ b/proto/compute_driver.proto @@ -65,6 +65,46 @@ service ComputeDriver { rpc DeleteWorkspace(DeleteWorkspaceRequest) returns (DeleteWorkspaceResponse); } +// Optional image-update extension, served on the same endpoint as ComputeDriver. +// Keeping this separate preserves the baseline generated server trait for +// independently versioned external drivers. Drivers advertise +// openshell.compute.update-sandbox-image only when they serve this service; +// gateways must negotiate that capability before calling it. +service ComputeDriverImageUpdates { + // Idempotently replace only the workload image of a stopped sandbox, retaining + // its stable resource and storage. Unsupported drivers omit this service and + // return UNIMPLEMENTED without changing any resource. + rpc UpdateSandboxImage(UpdateSandboxImageRequest) + returns (UpdateSandboxImageResponse); +} + +// Optional extension; never send configuration changes through the image-only RPC. +service ComputeDriverConfigurationUpdates { + rpc UpdateSandboxConfiguration(UpdateSandboxConfigurationRequest) + returns (UpdateSandboxConfigurationResponse); +} + +message UpdateSandboxConfigurationRequest { + // Shared lifecycle, image, revision and runtime identity preconditions. + UpdateSandboxImageRequest image_update = 1; + // Absent leaves the command unchanged; empty restores the default shell. + SandboxMainProcessUpdate main_process = 2; + // Effective user environment after template inheritance. Presence replaces it. + SandboxEnvironmentUpdate environment = 3; +} + +// Only workload argv and its existing TTY setting are carried to the driver. +message SandboxMainProcessUpdate { + repeated string command = 1; + bool tty = 2; +} + +message SandboxEnvironmentUpdate { + map values = 1; +} + +message UpdateSandboxConfigurationResponse {} + message GetCapabilitiesRequest { // Gateway protocol metadata. Drivers must reject unmet requirements. openshell.extension.v1.PeerMetadata gateway = 1; @@ -432,6 +472,27 @@ message CreateSandboxResponse { string runtime_identity = 1; } +message UpdateSandboxImageRequest { + string sandbox_id = 1; + string name = 2; + string image = 3; + // Pre-update image. A different current image conflicts unless durable driver + // evidence identifies this exact operation as already applied. + string expected_image = 4; + // Never adopt a same-name replacement resource. + string expected_runtime_identity = 5; + // Stable UUID across retries, recorded atomically with the image change. + string operation_id = 6; + // Perform all admission and state checks without writing any resource. The + // gateway uses this before recording durable intent; apply rechecks them. + bool validate_only = 7; + // Monotonic sandbox image revision. The driver atomically records it with + // the image and rejects earlier revisions even if expected_image matches. + uint64 revision = 8; +} + +message UpdateSandboxImageResponse {} + message StopSandboxRequest { // Stable sandbox ID stored by the gateway. string sandbox_id = 1; diff --git a/proto/openshell.proto b/proto/openshell.proto index 44d5ed81ee..071e19f7c5 100644 --- a/proto/openshell.proto +++ b/proto/openshell.proto @@ -838,6 +838,22 @@ service OpenShell { } } +// Optional sandbox mutations are separate from OpenShell so existing generated +// OpenShell server implementations remain source-compatible. +service SandboxUpdates { + // Update the workload image, command, environment, labels, or annotations of a + // stopped sandbox while preserving its identity and retained storage. Requires + // compute-driver support for the selected changes and explicit gateway activation + // of sandbox updates. + rpc UpdateSandbox(UpdateSandboxRequest) returns (SandboxResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } +} + // IssueSandboxToken request. Empty body; identity is established by the // authentication credentials carried in the request headers (a projected // Kubernetes ServiceAccount JWT in the K8s driver path). @@ -1305,6 +1321,39 @@ message CreateSandboxRequest { repeated SandboxServiceExposure service_exposures = 9; } +// Update selected configuration of a stopped sandbox. Omitted fields are unchanged. +// Start explicitly after a successful update. At least one field must be selected. +message UpdateSandboxRequest { + // Workspace scope. Only a named workspace selection is accepted. + openshell.datamodel.v1.WorkspaceSelector workspace_scope = 1; + // Canonical name of the existing sandbox; this is not a rename or upsert. + string name = 2; + // Nonempty OCI workload image reference. This does not change trusted runtime + // images or reseed an existing workspace. + optional string image = 3; + // Replace workload argv; an empty list restores image-resolved default shell behavior. + SandboxCommandUpdate command = 6; + // Replace sandbox-level overrides. Empty clears overrides; template defaults remain. + SandboxMapUpdate environment = 7; + // Replace user metadata, preserving gateway-owned keys. These do not set pod metadata. + SandboxMapUpdate labels = 8; + SandboxMapUpdate annotations = 9; + // Required nonzero resource version read by the caller. + uint64 expected_resource_version = 4; + // Optional nonzero UUID for durable at-most-once admission. + string request_id = 5; +} + +// Presence selects replacement, including an explicitly empty list. +message SandboxCommandUpdate { + repeated string argv = 1; +} + +// Presence selects replacement, including an explicitly empty map. +message SandboxMapUpdate { + map values = 1; +} + message CreateSandboxTemplateRequest { // Workspace scope. Only a named workspace selection is accepted. openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2; diff --git a/sdk/go/openshell/v1/internal/converter/coverage_test.go b/sdk/go/openshell/v1/internal/converter/coverage_test.go index 785e12c9c6..8a457d0014 100644 --- a/sdk/go/openshell/v1/internal/converter/coverage_test.go +++ b/sdk/go/openshell/v1/internal/converter/coverage_test.go @@ -135,7 +135,9 @@ func TestConverterCoversAllProtoFields_SandboxStatus(t *testing.T) { // Provisioning carries gateway-owned attempt, deadline, and cleanup state; // the curated API exposes its outcome through phase and conditions. Detailed // lifecycle bookkeeping remains available through the raw protobuf API. - skipped := fieldSet{"main_process_instance_id": true, "configuration_activated": true, "provisioning": true} + // Image updates are raw-only; their pending reconciliation intent and + // monotonic image revision bookkeeping are not part of the curated status model. + skipped := fieldSet{"main_process_instance_id": true, "configuration_activated": true, "provisioning": true, "image_update": true, "image_revision": true} assertAllFieldsCovered(t, (&pb.SandboxStatus{}).ProtoReflect().Descriptor(), handled, skipped) } diff --git a/sdk/go/proto/openshellv1/openshell.pb.go b/sdk/go/proto/openshellv1/openshell.pb.go index f180617bf6..25e2f3685b 100644 --- a/sdk/go/proto/openshellv1/openshell.pb.go +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -3686,6 +3686,215 @@ func (x *CreateSandboxRequest) GetServiceExposures() []*SandboxServiceExposure { return nil } +// Update selected configuration of a stopped sandbox. Omitted fields are unchanged. +// Start explicitly after a successful update. At least one field must be selected. +type UpdateSandboxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,1,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + // Canonical name of the existing sandbox; this is not a rename or upsert. + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // Nonempty OCI workload image reference. This does not change trusted runtime + // images or reseed an existing workspace. + Image *string `protobuf:"bytes,3,opt,name=image,proto3,oneof" json:"image,omitempty"` + // Replace workload argv; an empty list restores image-resolved default shell behavior. + Command *SandboxCommandUpdate `protobuf:"bytes,6,opt,name=command,proto3" json:"command,omitempty"` + // Replace sandbox-level overrides. Empty clears overrides; template defaults remain. + Environment *SandboxMapUpdate `protobuf:"bytes,7,opt,name=environment,proto3" json:"environment,omitempty"` + // Replace user metadata, preserving gateway-owned keys. These do not set pod metadata. + Labels *SandboxMapUpdate `protobuf:"bytes,8,opt,name=labels,proto3" json:"labels,omitempty"` + Annotations *SandboxMapUpdate `protobuf:"bytes,9,opt,name=annotations,proto3" json:"annotations,omitempty"` + // Required nonzero resource version read by the caller. + ExpectedResourceVersion uint64 `protobuf:"varint,4,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` + // Optional nonzero UUID for durable at-most-once admission. + RequestId string `protobuf:"bytes,5,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateSandboxRequest) Reset() { + *x = UpdateSandboxRequest{} + mi := &file_openshell_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateSandboxRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateSandboxRequest) ProtoMessage() {} + +func (x *UpdateSandboxRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[33] + 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 UpdateSandboxRequest.ProtoReflect.Descriptor instead. +func (*UpdateSandboxRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{33} +} + +func (x *UpdateSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { + if x != nil { + return x.WorkspaceScope + } + return nil +} + +func (x *UpdateSandboxRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UpdateSandboxRequest) GetImage() string { + if x != nil && x.Image != nil { + return *x.Image + } + return "" +} + +func (x *UpdateSandboxRequest) GetCommand() *SandboxCommandUpdate { + if x != nil { + return x.Command + } + return nil +} + +func (x *UpdateSandboxRequest) GetEnvironment() *SandboxMapUpdate { + if x != nil { + return x.Environment + } + return nil +} + +func (x *UpdateSandboxRequest) GetLabels() *SandboxMapUpdate { + if x != nil { + return x.Labels + } + return nil +} + +func (x *UpdateSandboxRequest) GetAnnotations() *SandboxMapUpdate { + if x != nil { + return x.Annotations + } + return nil +} + +func (x *UpdateSandboxRequest) GetExpectedResourceVersion() uint64 { + if x != nil { + return x.ExpectedResourceVersion + } + return 0 +} + +func (x *UpdateSandboxRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +// Presence selects replacement, including an explicitly empty list. +type SandboxCommandUpdate struct { + state protoimpl.MessageState `protogen:"open.v1"` + Argv []string `protobuf:"bytes,1,rep,name=argv,proto3" json:"argv,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxCommandUpdate) Reset() { + *x = SandboxCommandUpdate{} + mi := &file_openshell_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxCommandUpdate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxCommandUpdate) ProtoMessage() {} + +func (x *SandboxCommandUpdate) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[34] + 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 SandboxCommandUpdate.ProtoReflect.Descriptor instead. +func (*SandboxCommandUpdate) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{34} +} + +func (x *SandboxCommandUpdate) GetArgv() []string { + if x != nil { + return x.Argv + } + return nil +} + +// Presence selects replacement, including an explicitly empty map. +type SandboxMapUpdate struct { + state protoimpl.MessageState `protogen:"open.v1"` + Values map[string]string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxMapUpdate) Reset() { + *x = SandboxMapUpdate{} + mi := &file_openshell_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxMapUpdate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxMapUpdate) ProtoMessage() {} + +func (x *SandboxMapUpdate) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[35] + 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 SandboxMapUpdate.ProtoReflect.Descriptor instead. +func (*SandboxMapUpdate) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{35} +} + +func (x *SandboxMapUpdate) GetValues() map[string]string { + if x != nil { + return x.Values + } + return nil +} + type CreateSandboxTemplateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Workspace scope. Only a named workspace selection is accepted. @@ -3700,7 +3909,7 @@ type CreateSandboxTemplateRequest struct { func (x *CreateSandboxTemplateRequest) Reset() { *x = CreateSandboxTemplateRequest{} - mi := &file_openshell_proto_msgTypes[33] + mi := &file_openshell_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3712,7 +3921,7 @@ func (x *CreateSandboxTemplateRequest) String() string { func (*CreateSandboxTemplateRequest) ProtoMessage() {} func (x *CreateSandboxTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[33] + mi := &file_openshell_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3725,7 +3934,7 @@ func (x *CreateSandboxTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateSandboxTemplateRequest.ProtoReflect.Descriptor instead. func (*CreateSandboxTemplateRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{33} + return file_openshell_proto_rawDescGZIP(), []int{36} } func (x *CreateSandboxTemplateRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -3760,7 +3969,7 @@ type GetSandboxTemplateRequest struct { func (x *GetSandboxTemplateRequest) Reset() { *x = GetSandboxTemplateRequest{} - mi := &file_openshell_proto_msgTypes[34] + mi := &file_openshell_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3772,7 +3981,7 @@ func (x *GetSandboxTemplateRequest) String() string { func (*GetSandboxTemplateRequest) ProtoMessage() {} func (x *GetSandboxTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[34] + mi := &file_openshell_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3785,7 +3994,7 @@ func (x *GetSandboxTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxTemplateRequest.ProtoReflect.Descriptor instead. func (*GetSandboxTemplateRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{34} + return file_openshell_proto_rawDescGZIP(), []int{37} } func (x *GetSandboxTemplateRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -3820,7 +4029,7 @@ type ListSandboxTemplatesRequest struct { func (x *ListSandboxTemplatesRequest) Reset() { *x = ListSandboxTemplatesRequest{} - mi := &file_openshell_proto_msgTypes[35] + mi := &file_openshell_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3832,7 +4041,7 @@ func (x *ListSandboxTemplatesRequest) String() string { func (*ListSandboxTemplatesRequest) ProtoMessage() {} func (x *ListSandboxTemplatesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[35] + mi := &file_openshell_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3845,7 +4054,7 @@ func (x *ListSandboxTemplatesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxTemplatesRequest.ProtoReflect.Descriptor instead. func (*ListSandboxTemplatesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{35} + return file_openshell_proto_rawDescGZIP(), []int{38} } func (x *ListSandboxTemplatesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -3892,7 +4101,7 @@ type DeleteSandboxTemplateRequest struct { func (x *DeleteSandboxTemplateRequest) Reset() { *x = DeleteSandboxTemplateRequest{} - mi := &file_openshell_proto_msgTypes[36] + mi := &file_openshell_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3904,7 +4113,7 @@ func (x *DeleteSandboxTemplateRequest) String() string { func (*DeleteSandboxTemplateRequest) ProtoMessage() {} func (x *DeleteSandboxTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[36] + mi := &file_openshell_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3917,7 +4126,7 @@ func (x *DeleteSandboxTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteSandboxTemplateRequest.ProtoReflect.Descriptor instead. func (*DeleteSandboxTemplateRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{36} + return file_openshell_proto_rawDescGZIP(), []int{39} } func (x *DeleteSandboxTemplateRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -3957,7 +4166,7 @@ type SandboxTemplateResponse struct { func (x *SandboxTemplateResponse) Reset() { *x = SandboxTemplateResponse{} - mi := &file_openshell_proto_msgTypes[37] + mi := &file_openshell_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3969,7 +4178,7 @@ func (x *SandboxTemplateResponse) String() string { func (*SandboxTemplateResponse) ProtoMessage() {} func (x *SandboxTemplateResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[37] + mi := &file_openshell_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3982,7 +4191,7 @@ func (x *SandboxTemplateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxTemplateResponse.ProtoReflect.Descriptor instead. func (*SandboxTemplateResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{37} + return file_openshell_proto_rawDescGZIP(), []int{40} } func (x *SandboxTemplateResponse) GetTemplate() *SandboxWorkloadTemplate { @@ -4003,7 +4212,7 @@ type ListSandboxTemplatesResponse struct { func (x *ListSandboxTemplatesResponse) Reset() { *x = ListSandboxTemplatesResponse{} - mi := &file_openshell_proto_msgTypes[38] + mi := &file_openshell_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4015,7 +4224,7 @@ func (x *ListSandboxTemplatesResponse) String() string { func (*ListSandboxTemplatesResponse) ProtoMessage() {} func (x *ListSandboxTemplatesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[38] + mi := &file_openshell_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4028,7 +4237,7 @@ func (x *ListSandboxTemplatesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxTemplatesResponse.ProtoReflect.Descriptor instead. func (*ListSandboxTemplatesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{38} + return file_openshell_proto_rawDescGZIP(), []int{41} } func (x *ListSandboxTemplatesResponse) GetTemplates() []*SandboxWorkloadTemplate { @@ -4054,7 +4263,7 @@ type DeleteSandboxTemplateResponse struct { func (x *DeleteSandboxTemplateResponse) Reset() { *x = DeleteSandboxTemplateResponse{} - mi := &file_openshell_proto_msgTypes[39] + mi := &file_openshell_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4066,7 +4275,7 @@ func (x *DeleteSandboxTemplateResponse) String() string { func (*DeleteSandboxTemplateResponse) ProtoMessage() {} func (x *DeleteSandboxTemplateResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[39] + mi := &file_openshell_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4079,7 +4288,7 @@ func (x *DeleteSandboxTemplateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteSandboxTemplateResponse.ProtoReflect.Descriptor instead. func (*DeleteSandboxTemplateResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{39} + return file_openshell_proto_rawDescGZIP(), []int{42} } func (x *DeleteSandboxTemplateResponse) GetOutcome() DeletionOutcome { @@ -4106,7 +4315,7 @@ type BeginRootfsTarStagingRequest struct { func (x *BeginRootfsTarStagingRequest) Reset() { *x = BeginRootfsTarStagingRequest{} - mi := &file_openshell_proto_msgTypes[40] + mi := &file_openshell_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4118,7 +4327,7 @@ func (x *BeginRootfsTarStagingRequest) String() string { func (*BeginRootfsTarStagingRequest) ProtoMessage() {} func (x *BeginRootfsTarStagingRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[40] + mi := &file_openshell_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4131,7 +4340,7 @@ func (x *BeginRootfsTarStagingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BeginRootfsTarStagingRequest.ProtoReflect.Descriptor instead. func (*BeginRootfsTarStagingRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{40} + return file_openshell_proto_rawDescGZIP(), []int{43} } func (x *BeginRootfsTarStagingRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4174,7 +4383,7 @@ type BeginRootfsTarStagingResponse struct { func (x *BeginRootfsTarStagingResponse) Reset() { *x = BeginRootfsTarStagingResponse{} - mi := &file_openshell_proto_msgTypes[41] + mi := &file_openshell_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4186,7 +4395,7 @@ func (x *BeginRootfsTarStagingResponse) String() string { func (*BeginRootfsTarStagingResponse) ProtoMessage() {} func (x *BeginRootfsTarStagingResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[41] + mi := &file_openshell_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4199,7 +4408,7 @@ func (x *BeginRootfsTarStagingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BeginRootfsTarStagingResponse.ProtoReflect.Descriptor instead. func (*BeginRootfsTarStagingResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{41} + return file_openshell_proto_rawDescGZIP(), []int{44} } func (x *BeginRootfsTarStagingResponse) GetStagingToken() string { @@ -4242,7 +4451,7 @@ type GetSandboxRequest struct { func (x *GetSandboxRequest) Reset() { *x = GetSandboxRequest{} - mi := &file_openshell_proto_msgTypes[42] + mi := &file_openshell_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4254,7 +4463,7 @@ func (x *GetSandboxRequest) String() string { func (*GetSandboxRequest) ProtoMessage() {} func (x *GetSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[42] + mi := &file_openshell_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4267,7 +4476,7 @@ func (x *GetSandboxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxRequest.ProtoReflect.Descriptor instead. func (*GetSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{42} + return file_openshell_proto_rawDescGZIP(), []int{45} } func (x *GetSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4303,7 +4512,7 @@ type ListSandboxesRequest struct { func (x *ListSandboxesRequest) Reset() { *x = ListSandboxesRequest{} - mi := &file_openshell_proto_msgTypes[43] + mi := &file_openshell_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4315,7 +4524,7 @@ func (x *ListSandboxesRequest) String() string { func (*ListSandboxesRequest) ProtoMessage() {} func (x *ListSandboxesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[43] + mi := &file_openshell_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4328,7 +4537,7 @@ func (x *ListSandboxesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxesRequest.ProtoReflect.Descriptor instead. func (*ListSandboxesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{43} + return file_openshell_proto_rawDescGZIP(), []int{46} } func (x *ListSandboxesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4377,7 +4586,7 @@ type ListSandboxProvidersRequest struct { func (x *ListSandboxProvidersRequest) Reset() { *x = ListSandboxProvidersRequest{} - mi := &file_openshell_proto_msgTypes[44] + mi := &file_openshell_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4389,7 +4598,7 @@ func (x *ListSandboxProvidersRequest) String() string { func (*ListSandboxProvidersRequest) ProtoMessage() {} func (x *ListSandboxProvidersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[44] + mi := &file_openshell_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4402,7 +4611,7 @@ func (x *ListSandboxProvidersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxProvidersRequest.ProtoReflect.Descriptor instead. func (*ListSandboxProvidersRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{44} + return file_openshell_proto_rawDescGZIP(), []int{47} } func (x *ListSandboxProvidersRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4455,7 +4664,7 @@ type AttachSandboxProviderRequest struct { func (x *AttachSandboxProviderRequest) Reset() { *x = AttachSandboxProviderRequest{} - mi := &file_openshell_proto_msgTypes[45] + mi := &file_openshell_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4467,7 +4676,7 @@ func (x *AttachSandboxProviderRequest) String() string { func (*AttachSandboxProviderRequest) ProtoMessage() {} func (x *AttachSandboxProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[45] + mi := &file_openshell_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4480,7 +4689,7 @@ func (x *AttachSandboxProviderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachSandboxProviderRequest.ProtoReflect.Descriptor instead. func (*AttachSandboxProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{45} + return file_openshell_proto_rawDescGZIP(), []int{48} } func (x *AttachSandboxProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4540,7 +4749,7 @@ type DetachSandboxProviderRequest struct { func (x *DetachSandboxProviderRequest) Reset() { *x = DetachSandboxProviderRequest{} - mi := &file_openshell_proto_msgTypes[46] + mi := &file_openshell_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4552,7 +4761,7 @@ func (x *DetachSandboxProviderRequest) String() string { func (*DetachSandboxProviderRequest) ProtoMessage() {} func (x *DetachSandboxProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[46] + mi := &file_openshell_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4565,7 +4774,7 @@ func (x *DetachSandboxProviderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DetachSandboxProviderRequest.ProtoReflect.Descriptor instead. func (*DetachSandboxProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{46} + return file_openshell_proto_rawDescGZIP(), []int{49} } func (x *DetachSandboxProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4622,7 +4831,7 @@ type DeleteSandboxRequest struct { func (x *DeleteSandboxRequest) Reset() { *x = DeleteSandboxRequest{} - mi := &file_openshell_proto_msgTypes[47] + mi := &file_openshell_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4634,7 +4843,7 @@ func (x *DeleteSandboxRequest) String() string { func (*DeleteSandboxRequest) ProtoMessage() {} func (x *DeleteSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[47] + mi := &file_openshell_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4647,7 +4856,7 @@ func (x *DeleteSandboxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteSandboxRequest.ProtoReflect.Descriptor instead. func (*DeleteSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{47} + return file_openshell_proto_rawDescGZIP(), []int{50} } func (x *DeleteSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4693,7 +4902,7 @@ type StopSandboxRequest struct { func (x *StopSandboxRequest) Reset() { *x = StopSandboxRequest{} - mi := &file_openshell_proto_msgTypes[48] + mi := &file_openshell_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4705,7 +4914,7 @@ func (x *StopSandboxRequest) String() string { func (*StopSandboxRequest) ProtoMessage() {} func (x *StopSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[48] + mi := &file_openshell_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4718,7 +4927,7 @@ func (x *StopSandboxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StopSandboxRequest.ProtoReflect.Descriptor instead. func (*StopSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{48} + return file_openshell_proto_rawDescGZIP(), []int{51} } func (x *StopSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4757,7 +4966,7 @@ type StartSandboxRequest struct { func (x *StartSandboxRequest) Reset() { *x = StartSandboxRequest{} - mi := &file_openshell_proto_msgTypes[49] + mi := &file_openshell_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4769,7 +4978,7 @@ func (x *StartSandboxRequest) String() string { func (*StartSandboxRequest) ProtoMessage() {} func (x *StartSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[49] + mi := &file_openshell_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4782,7 +4991,7 @@ func (x *StartSandboxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StartSandboxRequest.ProtoReflect.Descriptor instead. func (*StartSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{49} + return file_openshell_proto_rawDescGZIP(), []int{52} } func (x *StartSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4819,7 +5028,7 @@ type SandboxResponse struct { func (x *SandboxResponse) Reset() { *x = SandboxResponse{} - mi := &file_openshell_proto_msgTypes[50] + mi := &file_openshell_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4831,7 +5040,7 @@ func (x *SandboxResponse) String() string { func (*SandboxResponse) ProtoMessage() {} func (x *SandboxResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[50] + mi := &file_openshell_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4844,7 +5053,7 @@ func (x *SandboxResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxResponse.ProtoReflect.Descriptor instead. func (*SandboxResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{50} + return file_openshell_proto_rawDescGZIP(), []int{53} } func (x *SandboxResponse) GetSandbox() *Sandbox { @@ -4873,7 +5082,7 @@ type ListSandboxesResponse struct { func (x *ListSandboxesResponse) Reset() { *x = ListSandboxesResponse{} - mi := &file_openshell_proto_msgTypes[51] + mi := &file_openshell_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4885,7 +5094,7 @@ func (x *ListSandboxesResponse) String() string { func (*ListSandboxesResponse) ProtoMessage() {} func (x *ListSandboxesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[51] + mi := &file_openshell_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4898,7 +5107,7 @@ func (x *ListSandboxesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxesResponse.ProtoReflect.Descriptor instead. func (*ListSandboxesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{51} + return file_openshell_proto_rawDescGZIP(), []int{54} } func (x *ListSandboxesResponse) GetSandboxes() []*Sandbox { @@ -4927,7 +5136,7 @@ type ListSandboxProvidersResponse struct { func (x *ListSandboxProvidersResponse) Reset() { *x = ListSandboxProvidersResponse{} - mi := &file_openshell_proto_msgTypes[52] + mi := &file_openshell_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4939,7 +5148,7 @@ func (x *ListSandboxProvidersResponse) String() string { func (*ListSandboxProvidersResponse) ProtoMessage() {} func (x *ListSandboxProvidersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[52] + mi := &file_openshell_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4952,7 +5161,7 @@ func (x *ListSandboxProvidersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxProvidersResponse.ProtoReflect.Descriptor instead. func (*ListSandboxProvidersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{52} + return file_openshell_proto_rawDescGZIP(), []int{55} } func (x *ListSandboxProvidersResponse) GetProviders() []*datamodelv1.Provider { @@ -4983,7 +5192,7 @@ type AttachSandboxProviderResponse struct { func (x *AttachSandboxProviderResponse) Reset() { *x = AttachSandboxProviderResponse{} - mi := &file_openshell_proto_msgTypes[53] + mi := &file_openshell_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4995,7 +5204,7 @@ func (x *AttachSandboxProviderResponse) String() string { func (*AttachSandboxProviderResponse) ProtoMessage() {} func (x *AttachSandboxProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[53] + mi := &file_openshell_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5008,7 +5217,7 @@ func (x *AttachSandboxProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachSandboxProviderResponse.ProtoReflect.Descriptor instead. func (*AttachSandboxProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{53} + return file_openshell_proto_rawDescGZIP(), []int{56} } func (x *AttachSandboxProviderResponse) GetSandbox() *Sandbox { @@ -5046,7 +5255,7 @@ type DetachSandboxProviderResponse struct { func (x *DetachSandboxProviderResponse) Reset() { *x = DetachSandboxProviderResponse{} - mi := &file_openshell_proto_msgTypes[54] + mi := &file_openshell_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5058,7 +5267,7 @@ func (x *DetachSandboxProviderResponse) String() string { func (*DetachSandboxProviderResponse) ProtoMessage() {} func (x *DetachSandboxProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[54] + mi := &file_openshell_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5071,7 +5280,7 @@ func (x *DetachSandboxProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DetachSandboxProviderResponse.ProtoReflect.Descriptor instead. func (*DetachSandboxProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{54} + return file_openshell_proto_rawDescGZIP(), []int{57} } func (x *DetachSandboxProviderResponse) GetSandbox() *Sandbox { @@ -5113,7 +5322,7 @@ type ProviderDesiredIdentity struct { func (x *ProviderDesiredIdentity) Reset() { *x = ProviderDesiredIdentity{} - mi := &file_openshell_proto_msgTypes[55] + mi := &file_openshell_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5125,7 +5334,7 @@ func (x *ProviderDesiredIdentity) String() string { func (*ProviderDesiredIdentity) ProtoMessage() {} func (x *ProviderDesiredIdentity) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[55] + mi := &file_openshell_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5138,7 +5347,7 @@ func (x *ProviderDesiredIdentity) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderDesiredIdentity.ProtoReflect.Descriptor instead. func (*ProviderDesiredIdentity) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{55} + return file_openshell_proto_rawDescGZIP(), []int{58} } func (x *ProviderDesiredIdentity) GetSandboxId() string { @@ -5213,7 +5422,7 @@ type ConfigSnapshotRevision struct { func (x *ConfigSnapshotRevision) Reset() { *x = ConfigSnapshotRevision{} - mi := &file_openshell_proto_msgTypes[56] + mi := &file_openshell_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5225,7 +5434,7 @@ func (x *ConfigSnapshotRevision) String() string { func (*ConfigSnapshotRevision) ProtoMessage() {} func (x *ConfigSnapshotRevision) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[56] + mi := &file_openshell_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5238,7 +5447,7 @@ func (x *ConfigSnapshotRevision) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigSnapshotRevision.ProtoReflect.Descriptor instead. func (*ConfigSnapshotRevision) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{56} + return file_openshell_proto_rawDescGZIP(), []int{59} } func (x *ConfigSnapshotRevision) GetComponent() isConfigSnapshotRevision_Component { @@ -5315,7 +5524,7 @@ type SandboxConfigRevision struct { func (x *SandboxConfigRevision) Reset() { *x = SandboxConfigRevision{} - mi := &file_openshell_proto_msgTypes[57] + mi := &file_openshell_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5327,7 +5536,7 @@ func (x *SandboxConfigRevision) String() string { func (*SandboxConfigRevision) ProtoMessage() {} func (x *SandboxConfigRevision) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[57] + mi := &file_openshell_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5340,7 +5549,7 @@ func (x *SandboxConfigRevision) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxConfigRevision.ProtoReflect.Descriptor instead. func (*SandboxConfigRevision) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{57} + return file_openshell_proto_rawDescGZIP(), []int{60} } func (x *SandboxConfigRevision) GetConfigRevision() uint64 { @@ -5399,7 +5608,7 @@ type ConfigUpdateOperation struct { func (x *ConfigUpdateOperation) Reset() { *x = ConfigUpdateOperation{} - mi := &file_openshell_proto_msgTypes[58] + mi := &file_openshell_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5411,7 +5620,7 @@ func (x *ConfigUpdateOperation) String() string { func (*ConfigUpdateOperation) ProtoMessage() {} func (x *ConfigUpdateOperation) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[58] + mi := &file_openshell_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5424,7 +5633,7 @@ func (x *ConfigUpdateOperation) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigUpdateOperation.ProtoReflect.Descriptor instead. func (*ConfigUpdateOperation) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{58} + return file_openshell_proto_rawDescGZIP(), []int{61} } func (x *ConfigUpdateOperation) GetOperationId() string { @@ -5514,7 +5723,7 @@ type ProviderMutationReceipt struct { func (x *ProviderMutationReceipt) Reset() { *x = ProviderMutationReceipt{} - mi := &file_openshell_proto_msgTypes[59] + mi := &file_openshell_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5526,7 +5735,7 @@ func (x *ProviderMutationReceipt) String() string { func (*ProviderMutationReceipt) ProtoMessage() {} func (x *ProviderMutationReceipt) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[59] + mi := &file_openshell_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5539,7 +5748,7 @@ func (x *ProviderMutationReceipt) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderMutationReceipt.ProtoReflect.Descriptor instead. func (*ProviderMutationReceipt) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{59} + return file_openshell_proto_rawDescGZIP(), []int{62} } func (x *ProviderMutationReceipt) GetReceiptId() string { @@ -5615,7 +5824,7 @@ type ProviderReadinessObservation struct { func (x *ProviderReadinessObservation) Reset() { *x = ProviderReadinessObservation{} - mi := &file_openshell_proto_msgTypes[60] + mi := &file_openshell_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5627,7 +5836,7 @@ func (x *ProviderReadinessObservation) String() string { func (*ProviderReadinessObservation) ProtoMessage() {} func (x *ProviderReadinessObservation) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[60] + mi := &file_openshell_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5640,7 +5849,7 @@ func (x *ProviderReadinessObservation) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderReadinessObservation.ProtoReflect.Descriptor instead. func (*ProviderReadinessObservation) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{60} + return file_openshell_proto_rawDescGZIP(), []int{63} } func (x *ProviderReadinessObservation) GetSessionId() string { @@ -5739,7 +5948,7 @@ type ProviderReadinessStatus struct { func (x *ProviderReadinessStatus) Reset() { *x = ProviderReadinessStatus{} - mi := &file_openshell_proto_msgTypes[61] + mi := &file_openshell_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5751,7 +5960,7 @@ func (x *ProviderReadinessStatus) String() string { func (*ProviderReadinessStatus) ProtoMessage() {} func (x *ProviderReadinessStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[61] + mi := &file_openshell_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5764,7 +5973,7 @@ func (x *ProviderReadinessStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderReadinessStatus.ProtoReflect.Descriptor instead. func (*ProviderReadinessStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{61} + return file_openshell_proto_rawDescGZIP(), []int{64} } func (x *ProviderReadinessStatus) GetReceipt() *ProviderMutationReceipt { @@ -5838,7 +6047,7 @@ type GetSandboxProviderStatusRequest struct { func (x *GetSandboxProviderStatusRequest) Reset() { *x = GetSandboxProviderStatusRequest{} - mi := &file_openshell_proto_msgTypes[62] + mi := &file_openshell_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5850,7 +6059,7 @@ func (x *GetSandboxProviderStatusRequest) String() string { func (*GetSandboxProviderStatusRequest) ProtoMessage() {} func (x *GetSandboxProviderStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[62] + mi := &file_openshell_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5863,7 +6072,7 @@ func (x *GetSandboxProviderStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxProviderStatusRequest.ProtoReflect.Descriptor instead. func (*GetSandboxProviderStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{62} + return file_openshell_proto_rawDescGZIP(), []int{65} } func (x *GetSandboxProviderStatusRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -5903,7 +6112,7 @@ type GetSandboxProviderStatusResponse struct { func (x *GetSandboxProviderStatusResponse) Reset() { *x = GetSandboxProviderStatusResponse{} - mi := &file_openshell_proto_msgTypes[63] + mi := &file_openshell_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5915,7 +6124,7 @@ func (x *GetSandboxProviderStatusResponse) String() string { func (*GetSandboxProviderStatusResponse) ProtoMessage() {} func (x *GetSandboxProviderStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[63] + mi := &file_openshell_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5928,7 +6137,7 @@ func (x *GetSandboxProviderStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxProviderStatusResponse.ProtoReflect.Descriptor instead. func (*GetSandboxProviderStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{63} + return file_openshell_proto_rawDescGZIP(), []int{66} } func (x *GetSandboxProviderStatusResponse) GetStatus() *ProviderReadinessStatus { @@ -5950,7 +6159,7 @@ type ReportProviderReadinessRequest struct { func (x *ReportProviderReadinessRequest) Reset() { *x = ReportProviderReadinessRequest{} - mi := &file_openshell_proto_msgTypes[64] + mi := &file_openshell_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5962,7 +6171,7 @@ func (x *ReportProviderReadinessRequest) String() string { func (*ReportProviderReadinessRequest) ProtoMessage() {} func (x *ReportProviderReadinessRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[64] + mi := &file_openshell_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5975,7 +6184,7 @@ func (x *ReportProviderReadinessRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportProviderReadinessRequest.ProtoReflect.Descriptor instead. func (*ReportProviderReadinessRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{64} + return file_openshell_proto_rawDescGZIP(), []int{67} } func (x *ReportProviderReadinessRequest) GetSandboxId() string { @@ -6005,7 +6214,7 @@ type ReportProviderReadinessResponse struct { func (x *ReportProviderReadinessResponse) Reset() { *x = ReportProviderReadinessResponse{} - mi := &file_openshell_proto_msgTypes[65] + mi := &file_openshell_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6017,7 +6226,7 @@ func (x *ReportProviderReadinessResponse) String() string { func (*ReportProviderReadinessResponse) ProtoMessage() {} func (x *ReportProviderReadinessResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[65] + mi := &file_openshell_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6030,7 +6239,7 @@ func (x *ReportProviderReadinessResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportProviderReadinessResponse.ProtoReflect.Descriptor instead. func (*ReportProviderReadinessResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{65} + return file_openshell_proto_rawDescGZIP(), []int{68} } func (x *ReportProviderReadinessResponse) GetAcceptedSequence() uint64 { @@ -6067,7 +6276,7 @@ type DeleteSandboxResponse struct { func (x *DeleteSandboxResponse) Reset() { *x = DeleteSandboxResponse{} - mi := &file_openshell_proto_msgTypes[66] + mi := &file_openshell_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6079,7 +6288,7 @@ func (x *DeleteSandboxResponse) String() string { func (*DeleteSandboxResponse) ProtoMessage() {} func (x *DeleteSandboxResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[66] + mi := &file_openshell_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6092,7 +6301,7 @@ func (x *DeleteSandboxResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteSandboxResponse.ProtoReflect.Descriptor instead. func (*DeleteSandboxResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{66} + return file_openshell_proto_rawDescGZIP(), []int{69} } func (x *DeleteSandboxResponse) GetOutcome() DeletionOutcome { @@ -6121,7 +6330,7 @@ type CreateSshSessionRequest struct { func (x *CreateSshSessionRequest) Reset() { *x = CreateSshSessionRequest{} - mi := &file_openshell_proto_msgTypes[67] + mi := &file_openshell_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6133,7 +6342,7 @@ func (x *CreateSshSessionRequest) String() string { func (*CreateSshSessionRequest) ProtoMessage() {} func (x *CreateSshSessionRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[67] + mi := &file_openshell_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6146,7 +6355,7 @@ func (x *CreateSshSessionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateSshSessionRequest.ProtoReflect.Descriptor instead. func (*CreateSshSessionRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{67} + return file_openshell_proto_rawDescGZIP(), []int{70} } func (x *CreateSshSessionRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -6197,7 +6406,7 @@ type CreateSshSessionResponse struct { func (x *CreateSshSessionResponse) Reset() { *x = CreateSshSessionResponse{} - mi := &file_openshell_proto_msgTypes[68] + mi := &file_openshell_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6209,7 +6418,7 @@ func (x *CreateSshSessionResponse) String() string { func (*CreateSshSessionResponse) ProtoMessage() {} func (x *CreateSshSessionResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[68] + mi := &file_openshell_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6222,7 +6431,7 @@ func (x *CreateSshSessionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateSshSessionResponse.ProtoReflect.Descriptor instead. func (*CreateSshSessionResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{68} + return file_openshell_proto_rawDescGZIP(), []int{71} } func (x *CreateSshSessionResponse) GetSandboxId() string { @@ -6297,7 +6506,7 @@ type ExposeServiceRequest struct { func (x *ExposeServiceRequest) Reset() { *x = ExposeServiceRequest{} - mi := &file_openshell_proto_msgTypes[69] + mi := &file_openshell_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6309,7 +6518,7 @@ func (x *ExposeServiceRequest) String() string { func (*ExposeServiceRequest) ProtoMessage() {} func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[69] + mi := &file_openshell_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6322,7 +6531,7 @@ func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExposeServiceRequest.ProtoReflect.Descriptor instead. func (*ExposeServiceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{69} + return file_openshell_proto_rawDescGZIP(), []int{72} } func (x *ExposeServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -6388,7 +6597,7 @@ type GetServiceRequest struct { func (x *GetServiceRequest) Reset() { *x = GetServiceRequest{} - mi := &file_openshell_proto_msgTypes[70] + mi := &file_openshell_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6400,7 +6609,7 @@ func (x *GetServiceRequest) String() string { func (*GetServiceRequest) ProtoMessage() {} func (x *GetServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[70] + mi := &file_openshell_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6413,7 +6622,7 @@ func (x *GetServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetServiceRequest.ProtoReflect.Descriptor instead. func (*GetServiceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{70} + return file_openshell_proto_rawDescGZIP(), []int{73} } func (x *GetServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -6456,7 +6665,7 @@ type ListServicesRequest struct { func (x *ListServicesRequest) Reset() { *x = ListServicesRequest{} - mi := &file_openshell_proto_msgTypes[71] + mi := &file_openshell_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6468,7 +6677,7 @@ func (x *ListServicesRequest) String() string { func (*ListServicesRequest) ProtoMessage() {} func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[71] + mi := &file_openshell_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6481,7 +6690,7 @@ func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListServicesRequest.ProtoReflect.Descriptor instead. func (*ListServicesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{71} + return file_openshell_proto_rawDescGZIP(), []int{74} } func (x *ListServicesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -6524,7 +6733,7 @@ type ListServicesResponse struct { func (x *ListServicesResponse) Reset() { *x = ListServicesResponse{} - mi := &file_openshell_proto_msgTypes[72] + mi := &file_openshell_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6536,7 +6745,7 @@ func (x *ListServicesResponse) String() string { func (*ListServicesResponse) ProtoMessage() {} func (x *ListServicesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[72] + mi := &file_openshell_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6549,7 +6758,7 @@ func (x *ListServicesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListServicesResponse.ProtoReflect.Descriptor instead. func (*ListServicesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{72} + return file_openshell_proto_rawDescGZIP(), []int{75} } func (x *ListServicesResponse) GetServices() []*ServiceEndpointResponse { @@ -6584,7 +6793,7 @@ type DeleteServiceRequest struct { func (x *DeleteServiceRequest) Reset() { *x = DeleteServiceRequest{} - mi := &file_openshell_proto_msgTypes[73] + mi := &file_openshell_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6596,7 +6805,7 @@ func (x *DeleteServiceRequest) String() string { func (*DeleteServiceRequest) ProtoMessage() {} func (x *DeleteServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[73] + mi := &file_openshell_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6609,7 +6818,7 @@ func (x *DeleteServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteServiceRequest.ProtoReflect.Descriptor instead. func (*DeleteServiceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{73} + return file_openshell_proto_rawDescGZIP(), []int{76} } func (x *DeleteServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -6657,7 +6866,7 @@ type DeleteServiceResponse struct { func (x *DeleteServiceResponse) Reset() { *x = DeleteServiceResponse{} - mi := &file_openshell_proto_msgTypes[74] + mi := &file_openshell_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6669,7 +6878,7 @@ func (x *DeleteServiceResponse) String() string { func (*DeleteServiceResponse) ProtoMessage() {} func (x *DeleteServiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[74] + mi := &file_openshell_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6682,7 +6891,7 @@ func (x *DeleteServiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteServiceResponse.ProtoReflect.Descriptor instead. func (*DeleteServiceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{74} + return file_openshell_proto_rawDescGZIP(), []int{77} } func (x *DeleteServiceResponse) GetOutcome() DeletionOutcome { @@ -6715,7 +6924,7 @@ type ServiceEndpoint struct { func (x *ServiceEndpoint) Reset() { *x = ServiceEndpoint{} - mi := &file_openshell_proto_msgTypes[75] + mi := &file_openshell_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6727,7 +6936,7 @@ func (x *ServiceEndpoint) String() string { func (*ServiceEndpoint) ProtoMessage() {} func (x *ServiceEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[75] + mi := &file_openshell_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6740,7 +6949,7 @@ func (x *ServiceEndpoint) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceEndpoint.ProtoReflect.Descriptor instead. func (*ServiceEndpoint) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{75} + return file_openshell_proto_rawDescGZIP(), []int{78} } func (x *ServiceEndpoint) GetMetadata() *datamodelv1.ObjectMeta { @@ -6803,7 +7012,7 @@ type ServiceEndpointResponse struct { func (x *ServiceEndpointResponse) Reset() { *x = ServiceEndpointResponse{} - mi := &file_openshell_proto_msgTypes[76] + mi := &file_openshell_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6815,7 +7024,7 @@ func (x *ServiceEndpointResponse) String() string { func (*ServiceEndpointResponse) ProtoMessage() {} func (x *ServiceEndpointResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[76] + mi := &file_openshell_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6828,7 +7037,7 @@ func (x *ServiceEndpointResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceEndpointResponse.ProtoReflect.Descriptor instead. func (*ServiceEndpointResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{76} + return file_openshell_proto_rawDescGZIP(), []int{79} } func (x *ServiceEndpointResponse) GetEndpoint() *ServiceEndpoint { @@ -6859,7 +7068,7 @@ type RevokeSshSessionRequest struct { func (x *RevokeSshSessionRequest) Reset() { *x = RevokeSshSessionRequest{} - mi := &file_openshell_proto_msgTypes[77] + mi := &file_openshell_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6871,7 +7080,7 @@ func (x *RevokeSshSessionRequest) String() string { func (*RevokeSshSessionRequest) ProtoMessage() {} func (x *RevokeSshSessionRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[77] + mi := &file_openshell_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6884,7 +7093,7 @@ func (x *RevokeSshSessionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RevokeSshSessionRequest.ProtoReflect.Descriptor instead. func (*RevokeSshSessionRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{77} + return file_openshell_proto_rawDescGZIP(), []int{80} } func (x *RevokeSshSessionRequest) GetToken() string { @@ -6911,7 +7120,7 @@ type RevokeSshSessionResponse struct { func (x *RevokeSshSessionResponse) Reset() { *x = RevokeSshSessionResponse{} - mi := &file_openshell_proto_msgTypes[78] + mi := &file_openshell_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6923,7 +7132,7 @@ func (x *RevokeSshSessionResponse) String() string { func (*RevokeSshSessionResponse) ProtoMessage() {} func (x *RevokeSshSessionResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[78] + mi := &file_openshell_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6936,7 +7145,7 @@ func (x *RevokeSshSessionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RevokeSshSessionResponse.ProtoReflect.Descriptor instead. func (*RevokeSshSessionResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{78} + return file_openshell_proto_rawDescGZIP(), []int{81} } func (x *RevokeSshSessionResponse) GetOutcome() DeletionOutcome { @@ -6986,7 +7195,7 @@ type ExecSandboxRequest struct { func (x *ExecSandboxRequest) Reset() { *x = ExecSandboxRequest{} - mi := &file_openshell_proto_msgTypes[79] + mi := &file_openshell_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6998,7 +7207,7 @@ func (x *ExecSandboxRequest) String() string { func (*ExecSandboxRequest) ProtoMessage() {} func (x *ExecSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[79] + mi := &file_openshell_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7011,7 +7220,7 @@ func (x *ExecSandboxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxRequest.ProtoReflect.Descriptor instead. func (*ExecSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{79} + return file_openshell_proto_rawDescGZIP(), []int{82} } func (x *ExecSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -7108,7 +7317,7 @@ type ExecSandboxStdout struct { func (x *ExecSandboxStdout) Reset() { *x = ExecSandboxStdout{} - mi := &file_openshell_proto_msgTypes[80] + mi := &file_openshell_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7120,7 +7329,7 @@ func (x *ExecSandboxStdout) String() string { func (*ExecSandboxStdout) ProtoMessage() {} func (x *ExecSandboxStdout) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[80] + mi := &file_openshell_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7133,7 +7342,7 @@ func (x *ExecSandboxStdout) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxStdout.ProtoReflect.Descriptor instead. func (*ExecSandboxStdout) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{80} + return file_openshell_proto_rawDescGZIP(), []int{83} } func (x *ExecSandboxStdout) GetData() []byte { @@ -7153,7 +7362,7 @@ type ExecSandboxStderr struct { func (x *ExecSandboxStderr) Reset() { *x = ExecSandboxStderr{} - mi := &file_openshell_proto_msgTypes[81] + mi := &file_openshell_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7165,7 +7374,7 @@ func (x *ExecSandboxStderr) String() string { func (*ExecSandboxStderr) ProtoMessage() {} func (x *ExecSandboxStderr) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[81] + mi := &file_openshell_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7178,7 +7387,7 @@ func (x *ExecSandboxStderr) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxStderr.ProtoReflect.Descriptor instead. func (*ExecSandboxStderr) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{81} + return file_openshell_proto_rawDescGZIP(), []int{84} } func (x *ExecSandboxStderr) GetData() []byte { @@ -7198,7 +7407,7 @@ type ExecSandboxExit struct { func (x *ExecSandboxExit) Reset() { *x = ExecSandboxExit{} - mi := &file_openshell_proto_msgTypes[82] + mi := &file_openshell_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7210,7 +7419,7 @@ func (x *ExecSandboxExit) String() string { func (*ExecSandboxExit) ProtoMessage() {} func (x *ExecSandboxExit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[82] + mi := &file_openshell_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7223,7 +7432,7 @@ func (x *ExecSandboxExit) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxExit.ProtoReflect.Descriptor instead. func (*ExecSandboxExit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{82} + return file_openshell_proto_rawDescGZIP(), []int{85} } func (x *ExecSandboxExit) GetExitCode() int32 { @@ -7248,7 +7457,7 @@ type ExecSandboxEvent struct { func (x *ExecSandboxEvent) Reset() { *x = ExecSandboxEvent{} - mi := &file_openshell_proto_msgTypes[83] + mi := &file_openshell_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7260,7 +7469,7 @@ func (x *ExecSandboxEvent) String() string { func (*ExecSandboxEvent) ProtoMessage() {} func (x *ExecSandboxEvent) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[83] + mi := &file_openshell_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7273,7 +7482,7 @@ func (x *ExecSandboxEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxEvent.ProtoReflect.Descriptor instead. func (*ExecSandboxEvent) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{83} + return file_openshell_proto_rawDescGZIP(), []int{86} } func (x *ExecSandboxEvent) GetPayload() isExecSandboxEvent_Payload { @@ -7355,7 +7564,7 @@ type TcpForwardInit struct { func (x *TcpForwardInit) Reset() { *x = TcpForwardInit{} - mi := &file_openshell_proto_msgTypes[84] + mi := &file_openshell_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7367,7 +7576,7 @@ func (x *TcpForwardInit) String() string { func (*TcpForwardInit) ProtoMessage() {} func (x *TcpForwardInit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[84] + mi := &file_openshell_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7380,7 +7589,7 @@ func (x *TcpForwardInit) ProtoReflect() protoreflect.Message { // Deprecated: Use TcpForwardInit.ProtoReflect.Descriptor instead. func (*TcpForwardInit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{84} + return file_openshell_proto_rawDescGZIP(), []int{87} } func (x *TcpForwardInit) GetSandbox() string { @@ -7466,7 +7675,7 @@ type TcpForwardFrame struct { func (x *TcpForwardFrame) Reset() { *x = TcpForwardFrame{} - mi := &file_openshell_proto_msgTypes[85] + mi := &file_openshell_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7478,7 +7687,7 @@ func (x *TcpForwardFrame) String() string { func (*TcpForwardFrame) ProtoMessage() {} func (x *TcpForwardFrame) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[85] + mi := &file_openshell_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7491,7 +7700,7 @@ func (x *TcpForwardFrame) ProtoReflect() protoreflect.Message { // Deprecated: Use TcpForwardFrame.ProtoReflect.Descriptor instead. func (*TcpForwardFrame) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{85} + return file_openshell_proto_rawDescGZIP(), []int{88} } func (x *TcpForwardFrame) GetPayload() isTcpForwardFrame_Payload { @@ -7550,7 +7759,7 @@ type ExecSandboxInput struct { func (x *ExecSandboxInput) Reset() { *x = ExecSandboxInput{} - mi := &file_openshell_proto_msgTypes[86] + mi := &file_openshell_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7562,7 +7771,7 @@ func (x *ExecSandboxInput) String() string { func (*ExecSandboxInput) ProtoMessage() {} func (x *ExecSandboxInput) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[86] + mi := &file_openshell_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7575,7 +7784,7 @@ func (x *ExecSandboxInput) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxInput.ProtoReflect.Descriptor instead. func (*ExecSandboxInput) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{86} + return file_openshell_proto_rawDescGZIP(), []int{89} } func (x *ExecSandboxInput) GetPayload() isExecSandboxInput_Payload { @@ -7648,7 +7857,7 @@ type ExecSandboxWindowResize struct { func (x *ExecSandboxWindowResize) Reset() { *x = ExecSandboxWindowResize{} - mi := &file_openshell_proto_msgTypes[87] + mi := &file_openshell_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7660,7 +7869,7 @@ func (x *ExecSandboxWindowResize) String() string { func (*ExecSandboxWindowResize) ProtoMessage() {} func (x *ExecSandboxWindowResize) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[87] + mi := &file_openshell_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7673,7 +7882,7 @@ func (x *ExecSandboxWindowResize) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxWindowResize.ProtoReflect.Descriptor instead. func (*ExecSandboxWindowResize) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{87} + return file_openshell_proto_rawDescGZIP(), []int{90} } func (x *ExecSandboxWindowResize) GetCols() uint32 { @@ -7709,7 +7918,7 @@ type SshSession struct { func (x *SshSession) Reset() { *x = SshSession{} - mi := &file_openshell_proto_msgTypes[88] + mi := &file_openshell_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7721,7 +7930,7 @@ func (x *SshSession) String() string { func (*SshSession) ProtoMessage() {} func (x *SshSession) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[88] + mi := &file_openshell_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7734,7 +7943,7 @@ func (x *SshSession) ProtoReflect() protoreflect.Message { // Deprecated: Use SshSession.ProtoReflect.Descriptor instead. func (*SshSession) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{88} + return file_openshell_proto_rawDescGZIP(), []int{91} } func (x *SshSession) GetMetadata() *datamodelv1.ObjectMeta { @@ -7823,7 +8032,7 @@ type WatchSandboxRequest struct { func (x *WatchSandboxRequest) Reset() { *x = WatchSandboxRequest{} - mi := &file_openshell_proto_msgTypes[89] + mi := &file_openshell_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7835,7 +8044,7 @@ func (x *WatchSandboxRequest) String() string { func (*WatchSandboxRequest) ProtoMessage() {} func (x *WatchSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[89] + mi := &file_openshell_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7848,7 +8057,7 @@ func (x *WatchSandboxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSandboxRequest.ProtoReflect.Descriptor instead. func (*WatchSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{89} + return file_openshell_proto_rawDescGZIP(), []int{92} } func (x *WatchSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -7964,7 +8173,7 @@ type SandboxStreamEvent struct { func (x *SandboxStreamEvent) Reset() { *x = SandboxStreamEvent{} - mi := &file_openshell_proto_msgTypes[90] + mi := &file_openshell_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7976,7 +8185,7 @@ func (x *SandboxStreamEvent) String() string { func (*SandboxStreamEvent) ProtoMessage() {} func (x *SandboxStreamEvent) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[90] + mi := &file_openshell_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7989,7 +8198,7 @@ func (x *SandboxStreamEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStreamEvent.ProtoReflect.Descriptor instead. func (*SandboxStreamEvent) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{90} + return file_openshell_proto_rawDescGZIP(), []int{93} } func (x *SandboxStreamEvent) GetPayload() isSandboxStreamEvent_Payload { @@ -8111,7 +8320,7 @@ type SandboxLogLine struct { func (x *SandboxLogLine) Reset() { *x = SandboxLogLine{} - mi := &file_openshell_proto_msgTypes[91] + mi := &file_openshell_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8123,7 +8332,7 @@ func (x *SandboxLogLine) String() string { func (*SandboxLogLine) ProtoMessage() {} func (x *SandboxLogLine) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[91] + mi := &file_openshell_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8136,7 +8345,7 @@ func (x *SandboxLogLine) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxLogLine.ProtoReflect.Descriptor instead. func (*SandboxLogLine) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{91} + return file_openshell_proto_rawDescGZIP(), []int{94} } func (x *SandboxLogLine) GetSandboxId() string { @@ -8202,7 +8411,7 @@ type SandboxStreamWarning struct { func (x *SandboxStreamWarning) Reset() { *x = SandboxStreamWarning{} - mi := &file_openshell_proto_msgTypes[92] + mi := &file_openshell_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8214,7 +8423,7 @@ func (x *SandboxStreamWarning) String() string { func (*SandboxStreamWarning) ProtoMessage() {} func (x *SandboxStreamWarning) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[92] + mi := &file_openshell_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8227,7 +8436,7 @@ func (x *SandboxStreamWarning) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStreamWarning.ProtoReflect.Descriptor instead. func (*SandboxStreamWarning) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{92} + return file_openshell_proto_rawDescGZIP(), []int{95} } func (x *SandboxStreamWarning) GetMessage() string { @@ -8252,7 +8461,7 @@ type CreateProviderRequest struct { func (x *CreateProviderRequest) Reset() { *x = CreateProviderRequest{} - mi := &file_openshell_proto_msgTypes[93] + mi := &file_openshell_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8264,7 +8473,7 @@ func (x *CreateProviderRequest) String() string { func (*CreateProviderRequest) ProtoMessage() {} func (x *CreateProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[93] + mi := &file_openshell_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8277,7 +8486,7 @@ func (x *CreateProviderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateProviderRequest.ProtoReflect.Descriptor instead. func (*CreateProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{93} + return file_openshell_proto_rawDescGZIP(), []int{96} } func (x *CreateProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -8313,7 +8522,7 @@ type GetProviderRequest struct { func (x *GetProviderRequest) Reset() { *x = GetProviderRequest{} - mi := &file_openshell_proto_msgTypes[94] + mi := &file_openshell_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8325,7 +8534,7 @@ func (x *GetProviderRequest) String() string { func (*GetProviderRequest) ProtoMessage() {} func (x *GetProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[94] + mi := &file_openshell_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8338,7 +8547,7 @@ func (x *GetProviderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderRequest.ProtoReflect.Descriptor instead. func (*GetProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{94} + return file_openshell_proto_rawDescGZIP(), []int{97} } func (x *GetProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -8373,7 +8582,7 @@ type GetProviderCredentialsRequest struct { func (x *GetProviderCredentialsRequest) Reset() { *x = GetProviderCredentialsRequest{} - mi := &file_openshell_proto_msgTypes[95] + mi := &file_openshell_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8385,7 +8594,7 @@ func (x *GetProviderCredentialsRequest) String() string { func (*GetProviderCredentialsRequest) ProtoMessage() {} func (x *GetProviderCredentialsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[95] + mi := &file_openshell_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8398,7 +8607,7 @@ func (x *GetProviderCredentialsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderCredentialsRequest.ProtoReflect.Descriptor instead. func (*GetProviderCredentialsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{95} + return file_openshell_proto_rawDescGZIP(), []int{98} } func (x *GetProviderCredentialsRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -8441,7 +8650,7 @@ type ProviderCredentialValue struct { func (x *ProviderCredentialValue) Reset() { *x = ProviderCredentialValue{} - mi := &file_openshell_proto_msgTypes[96] + mi := &file_openshell_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8453,7 +8662,7 @@ func (x *ProviderCredentialValue) String() string { func (*ProviderCredentialValue) ProtoMessage() {} func (x *ProviderCredentialValue) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[96] + mi := &file_openshell_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8466,7 +8675,7 @@ func (x *ProviderCredentialValue) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialValue.ProtoReflect.Descriptor instead. func (*ProviderCredentialValue) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{96} + return file_openshell_proto_rawDescGZIP(), []int{99} } func (x *ProviderCredentialValue) GetValue() string { @@ -8493,7 +8702,7 @@ type GetProviderCredentialsResponse struct { func (x *GetProviderCredentialsResponse) Reset() { *x = GetProviderCredentialsResponse{} - mi := &file_openshell_proto_msgTypes[97] + mi := &file_openshell_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8505,7 +8714,7 @@ func (x *GetProviderCredentialsResponse) String() string { func (*GetProviderCredentialsResponse) ProtoMessage() {} func (x *GetProviderCredentialsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[97] + mi := &file_openshell_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8518,7 +8727,7 @@ func (x *GetProviderCredentialsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderCredentialsResponse.ProtoReflect.Descriptor instead. func (*GetProviderCredentialsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{97} + return file_openshell_proto_rawDescGZIP(), []int{100} } func (x *GetProviderCredentialsResponse) GetCredentials() map[string]*ProviderCredentialValue { @@ -8547,7 +8756,7 @@ type ListProvidersRequest struct { func (x *ListProvidersRequest) Reset() { *x = ListProvidersRequest{} - mi := &file_openshell_proto_msgTypes[98] + mi := &file_openshell_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8559,7 +8768,7 @@ func (x *ListProvidersRequest) String() string { func (*ListProvidersRequest) ProtoMessage() {} func (x *ListProvidersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[98] + mi := &file_openshell_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8572,7 +8781,7 @@ func (x *ListProvidersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProvidersRequest.ProtoReflect.Descriptor instead. func (*ListProvidersRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{98} + return file_openshell_proto_rawDescGZIP(), []int{101} } func (x *ListProvidersRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -8624,7 +8833,7 @@ type UpdateProviderRequest struct { func (x *UpdateProviderRequest) Reset() { *x = UpdateProviderRequest{} - mi := &file_openshell_proto_msgTypes[99] + mi := &file_openshell_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8636,7 +8845,7 @@ func (x *UpdateProviderRequest) String() string { func (*UpdateProviderRequest) ProtoMessage() {} func (x *UpdateProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[99] + mi := &file_openshell_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8649,7 +8858,7 @@ func (x *UpdateProviderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderRequest.ProtoReflect.Descriptor instead. func (*UpdateProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{99} + return file_openshell_proto_rawDescGZIP(), []int{102} } func (x *UpdateProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -8703,7 +8912,7 @@ type DeleteProviderRequest struct { func (x *DeleteProviderRequest) Reset() { *x = DeleteProviderRequest{} - mi := &file_openshell_proto_msgTypes[100] + mi := &file_openshell_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8715,7 +8924,7 @@ func (x *DeleteProviderRequest) String() string { func (*DeleteProviderRequest) ProtoMessage() {} func (x *DeleteProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[100] + mi := &file_openshell_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8728,7 +8937,7 @@ func (x *DeleteProviderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{100} + return file_openshell_proto_rawDescGZIP(), []int{103} } func (x *DeleteProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -8774,7 +8983,7 @@ type ProviderResponse struct { func (x *ProviderResponse) Reset() { *x = ProviderResponse{} - mi := &file_openshell_proto_msgTypes[101] + mi := &file_openshell_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8786,7 +8995,7 @@ func (x *ProviderResponse) String() string { func (*ProviderResponse) ProtoMessage() {} func (x *ProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[101] + mi := &file_openshell_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8799,7 +9008,7 @@ func (x *ProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderResponse.ProtoReflect.Descriptor instead. func (*ProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{101} + return file_openshell_proto_rawDescGZIP(), []int{104} } func (x *ProviderResponse) GetProvider() *datamodelv1.Provider { @@ -8835,7 +9044,7 @@ type ListProvidersResponse struct { func (x *ListProvidersResponse) Reset() { *x = ListProvidersResponse{} - mi := &file_openshell_proto_msgTypes[102] + mi := &file_openshell_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8847,7 +9056,7 @@ func (x *ListProvidersResponse) String() string { func (*ListProvidersResponse) ProtoMessage() {} func (x *ListProvidersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[102] + mi := &file_openshell_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8860,7 +9069,7 @@ func (x *ListProvidersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProvidersResponse.ProtoReflect.Descriptor instead. func (*ListProvidersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{102} + return file_openshell_proto_rawDescGZIP(), []int{105} } func (x *ListProvidersResponse) GetProviders() []*datamodelv1.Provider { @@ -8894,7 +9103,7 @@ type ListProviderProfilesRequest struct { func (x *ListProviderProfilesRequest) Reset() { *x = ListProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[103] + mi := &file_openshell_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8906,7 +9115,7 @@ func (x *ListProviderProfilesRequest) String() string { func (*ListProviderProfilesRequest) ProtoMessage() {} func (x *ListProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[103] + mi := &file_openshell_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8919,7 +9128,7 @@ func (x *ListProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*ListProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{103} + return file_openshell_proto_rawDescGZIP(), []int{106} } func (x *ListProviderProfilesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -8955,7 +9164,7 @@ type GetProviderProfileRequest struct { func (x *GetProviderProfileRequest) Reset() { *x = GetProviderProfileRequest{} - mi := &file_openshell_proto_msgTypes[104] + mi := &file_openshell_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8967,7 +9176,7 @@ func (x *GetProviderProfileRequest) String() string { func (*GetProviderProfileRequest) ProtoMessage() {} func (x *GetProviderProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[104] + mi := &file_openshell_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8980,7 +9189,7 @@ func (x *GetProviderProfileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderProfileRequest.ProtoReflect.Descriptor instead. func (*GetProviderProfileRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{104} + return file_openshell_proto_rawDescGZIP(), []int{107} } func (x *GetProviderProfileRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -9008,7 +9217,7 @@ type ProviderProfileImportItem struct { func (x *ProviderProfileImportItem) Reset() { *x = ProviderProfileImportItem{} - mi := &file_openshell_proto_msgTypes[105] + mi := &file_openshell_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9020,7 +9229,7 @@ func (x *ProviderProfileImportItem) String() string { func (*ProviderProfileImportItem) ProtoMessage() {} func (x *ProviderProfileImportItem) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[105] + mi := &file_openshell_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9033,7 +9242,7 @@ func (x *ProviderProfileImportItem) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileImportItem.ProtoReflect.Descriptor instead. func (*ProviderProfileImportItem) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{105} + return file_openshell_proto_rawDescGZIP(), []int{108} } func (x *ProviderProfileImportItem) GetProfile() *ProviderProfile { @@ -9064,7 +9273,7 @@ type ProviderProfileDiagnostic struct { func (x *ProviderProfileDiagnostic) Reset() { *x = ProviderProfileDiagnostic{} - mi := &file_openshell_proto_msgTypes[106] + mi := &file_openshell_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9076,7 +9285,7 @@ func (x *ProviderProfileDiagnostic) String() string { func (*ProviderProfileDiagnostic) ProtoMessage() {} func (x *ProviderProfileDiagnostic) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[106] + mi := &file_openshell_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9089,7 +9298,7 @@ func (x *ProviderProfileDiagnostic) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileDiagnostic.ProtoReflect.Descriptor instead. func (*ProviderProfileDiagnostic) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{106} + return file_openshell_proto_rawDescGZIP(), []int{109} } func (x *ProviderProfileDiagnostic) GetSource() string { @@ -9146,7 +9355,7 @@ type ProviderCredentialTokenGrantAudienceOverride struct { func (x *ProviderCredentialTokenGrantAudienceOverride) Reset() { *x = ProviderCredentialTokenGrantAudienceOverride{} - mi := &file_openshell_proto_msgTypes[107] + mi := &file_openshell_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9158,7 +9367,7 @@ func (x *ProviderCredentialTokenGrantAudienceOverride) String() string { func (*ProviderCredentialTokenGrantAudienceOverride) ProtoMessage() {} func (x *ProviderCredentialTokenGrantAudienceOverride) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[107] + mi := &file_openshell_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9171,7 +9380,7 @@ func (x *ProviderCredentialTokenGrantAudienceOverride) ProtoReflect() protorefle // Deprecated: Use ProviderCredentialTokenGrantAudienceOverride.ProtoReflect.Descriptor instead. func (*ProviderCredentialTokenGrantAudienceOverride) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{107} + return file_openshell_proto_rawDescGZIP(), []int{110} } func (x *ProviderCredentialTokenGrantAudienceOverride) GetHost() string { @@ -9225,7 +9434,7 @@ type ProviderCredentialTokenGrantSubjectToken struct { func (x *ProviderCredentialTokenGrantSubjectToken) Reset() { *x = ProviderCredentialTokenGrantSubjectToken{} - mi := &file_openshell_proto_msgTypes[108] + mi := &file_openshell_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9237,7 +9446,7 @@ func (x *ProviderCredentialTokenGrantSubjectToken) String() string { func (*ProviderCredentialTokenGrantSubjectToken) ProtoMessage() {} func (x *ProviderCredentialTokenGrantSubjectToken) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[108] + mi := &file_openshell_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9250,7 +9459,7 @@ func (x *ProviderCredentialTokenGrantSubjectToken) ProtoReflect() protoreflect.M // Deprecated: Use ProviderCredentialTokenGrantSubjectToken.ProtoReflect.Descriptor instead. func (*ProviderCredentialTokenGrantSubjectToken) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{108} + return file_openshell_proto_rawDescGZIP(), []int{111} } func (x *ProviderCredentialTokenGrantSubjectToken) GetSource() string { @@ -9306,7 +9515,7 @@ type ProviderCredentialTokenGrant struct { func (x *ProviderCredentialTokenGrant) Reset() { *x = ProviderCredentialTokenGrant{} - mi := &file_openshell_proto_msgTypes[109] + mi := &file_openshell_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9318,7 +9527,7 @@ func (x *ProviderCredentialTokenGrant) String() string { func (*ProviderCredentialTokenGrant) ProtoMessage() {} func (x *ProviderCredentialTokenGrant) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[109] + mi := &file_openshell_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9331,7 +9540,7 @@ func (x *ProviderCredentialTokenGrant) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialTokenGrant.ProtoReflect.Descriptor instead. func (*ProviderCredentialTokenGrant) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{109} + return file_openshell_proto_rawDescGZIP(), []int{112} } func (x *ProviderCredentialTokenGrant) GetTokenEndpoint() string { @@ -9435,7 +9644,7 @@ type ProviderProfileCredential struct { func (x *ProviderProfileCredential) Reset() { *x = ProviderProfileCredential{} - mi := &file_openshell_proto_msgTypes[110] + mi := &file_openshell_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9447,7 +9656,7 @@ func (x *ProviderProfileCredential) String() string { func (*ProviderProfileCredential) ProtoMessage() {} func (x *ProviderProfileCredential) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[110] + mi := &file_openshell_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9460,7 +9669,7 @@ func (x *ProviderProfileCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileCredential.ProtoReflect.Descriptor instead. func (*ProviderProfileCredential) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{110} + return file_openshell_proto_rawDescGZIP(), []int{113} } func (x *ProviderProfileCredential) GetName() string { @@ -9559,7 +9768,7 @@ type ProviderCredentialRefreshMaterial struct { func (x *ProviderCredentialRefreshMaterial) Reset() { *x = ProviderCredentialRefreshMaterial{} - mi := &file_openshell_proto_msgTypes[111] + mi := &file_openshell_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9571,7 +9780,7 @@ func (x *ProviderCredentialRefreshMaterial) String() string { func (*ProviderCredentialRefreshMaterial) ProtoMessage() {} func (x *ProviderCredentialRefreshMaterial) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[111] + mi := &file_openshell_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9584,7 +9793,7 @@ func (x *ProviderCredentialRefreshMaterial) ProtoReflect() protoreflect.Message // Deprecated: Use ProviderCredentialRefreshMaterial.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefreshMaterial) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{111} + return file_openshell_proto_rawDescGZIP(), []int{114} } func (x *ProviderCredentialRefreshMaterial) GetName() string { @@ -9629,7 +9838,7 @@ type ProviderCredentialRefreshOutput struct { func (x *ProviderCredentialRefreshOutput) Reset() { *x = ProviderCredentialRefreshOutput{} - mi := &file_openshell_proto_msgTypes[112] + mi := &file_openshell_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9641,7 +9850,7 @@ func (x *ProviderCredentialRefreshOutput) String() string { func (*ProviderCredentialRefreshOutput) ProtoMessage() {} func (x *ProviderCredentialRefreshOutput) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[112] + mi := &file_openshell_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9654,7 +9863,7 @@ func (x *ProviderCredentialRefreshOutput) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialRefreshOutput.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefreshOutput) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{112} + return file_openshell_proto_rawDescGZIP(), []int{115} } func (x *ProviderCredentialRefreshOutput) GetOutput() string { @@ -9686,7 +9895,7 @@ type ProviderCredentialRefresh struct { func (x *ProviderCredentialRefresh) Reset() { *x = ProviderCredentialRefresh{} - mi := &file_openshell_proto_msgTypes[113] + mi := &file_openshell_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9698,7 +9907,7 @@ func (x *ProviderCredentialRefresh) String() string { func (*ProviderCredentialRefresh) ProtoMessage() {} func (x *ProviderCredentialRefresh) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[113] + mi := &file_openshell_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9711,7 +9920,7 @@ func (x *ProviderCredentialRefresh) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialRefresh.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefresh) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{113} + return file_openshell_proto_rawDescGZIP(), []int{116} } func (x *ProviderCredentialRefresh) GetStrategy() ProviderCredentialRefreshStrategy { @@ -9792,7 +10001,7 @@ type ProviderCredentialRefreshStatus struct { func (x *ProviderCredentialRefreshStatus) Reset() { *x = ProviderCredentialRefreshStatus{} - mi := &file_openshell_proto_msgTypes[114] + mi := &file_openshell_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9804,7 +10013,7 @@ func (x *ProviderCredentialRefreshStatus) String() string { func (*ProviderCredentialRefreshStatus) ProtoMessage() {} func (x *ProviderCredentialRefreshStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[114] + mi := &file_openshell_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9817,7 +10026,7 @@ func (x *ProviderCredentialRefreshStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialRefreshStatus.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefreshStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{114} + return file_openshell_proto_rawDescGZIP(), []int{117} } func (x *ProviderCredentialRefreshStatus) GetProvider() string { @@ -9922,7 +10131,7 @@ type ProviderProfileDiscovery struct { func (x *ProviderProfileDiscovery) Reset() { *x = ProviderProfileDiscovery{} - mi := &file_openshell_proto_msgTypes[115] + mi := &file_openshell_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9934,7 +10143,7 @@ func (x *ProviderProfileDiscovery) String() string { func (*ProviderProfileDiscovery) ProtoMessage() {} func (x *ProviderProfileDiscovery) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[115] + mi := &file_openshell_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9947,7 +10156,7 @@ func (x *ProviderProfileDiscovery) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileDiscovery.ProtoReflect.Descriptor instead. func (*ProviderProfileDiscovery) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{115} + return file_openshell_proto_rawDescGZIP(), []int{118} } func (x *ProviderProfileDiscovery) GetCredentials() []string { @@ -9969,7 +10178,7 @@ type GetProviderRefreshStatusRequest struct { func (x *GetProviderRefreshStatusRequest) Reset() { *x = GetProviderRefreshStatusRequest{} - mi := &file_openshell_proto_msgTypes[116] + mi := &file_openshell_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9981,7 +10190,7 @@ func (x *GetProviderRefreshStatusRequest) String() string { func (*GetProviderRefreshStatusRequest) ProtoMessage() {} func (x *GetProviderRefreshStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[116] + mi := &file_openshell_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9994,7 +10203,7 @@ func (x *GetProviderRefreshStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderRefreshStatusRequest.ProtoReflect.Descriptor instead. func (*GetProviderRefreshStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{116} + return file_openshell_proto_rawDescGZIP(), []int{119} } func (x *GetProviderRefreshStatusRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10027,7 +10236,7 @@ type GetProviderRefreshStatusResponse struct { func (x *GetProviderRefreshStatusResponse) Reset() { *x = GetProviderRefreshStatusResponse{} - mi := &file_openshell_proto_msgTypes[117] + mi := &file_openshell_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10039,7 +10248,7 @@ func (x *GetProviderRefreshStatusResponse) String() string { func (*GetProviderRefreshStatusResponse) ProtoMessage() {} func (x *GetProviderRefreshStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[117] + mi := &file_openshell_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10052,7 +10261,7 @@ func (x *GetProviderRefreshStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderRefreshStatusResponse.ProtoReflect.Descriptor instead. func (*GetProviderRefreshStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{117} + return file_openshell_proto_rawDescGZIP(), []int{120} } func (x *GetProviderRefreshStatusResponse) GetCredentials() []*ProviderCredentialRefreshStatus { @@ -10084,7 +10293,7 @@ type ConfigureProviderRefreshRequest struct { func (x *ConfigureProviderRefreshRequest) Reset() { *x = ConfigureProviderRefreshRequest{} - mi := &file_openshell_proto_msgTypes[118] + mi := &file_openshell_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10096,7 +10305,7 @@ func (x *ConfigureProviderRefreshRequest) String() string { func (*ConfigureProviderRefreshRequest) ProtoMessage() {} func (x *ConfigureProviderRefreshRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[118] + mi := &file_openshell_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10109,7 +10318,7 @@ func (x *ConfigureProviderRefreshRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigureProviderRefreshRequest.ProtoReflect.Descriptor instead. func (*ConfigureProviderRefreshRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{118} + return file_openshell_proto_rawDescGZIP(), []int{121} } func (x *ConfigureProviderRefreshRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10177,7 +10386,7 @@ type ConfigureProviderRefreshResponse struct { func (x *ConfigureProviderRefreshResponse) Reset() { *x = ConfigureProviderRefreshResponse{} - mi := &file_openshell_proto_msgTypes[119] + mi := &file_openshell_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10189,7 +10398,7 @@ func (x *ConfigureProviderRefreshResponse) String() string { func (*ConfigureProviderRefreshResponse) ProtoMessage() {} func (x *ConfigureProviderRefreshResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[119] + mi := &file_openshell_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10202,7 +10411,7 @@ func (x *ConfigureProviderRefreshResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigureProviderRefreshResponse.ProtoReflect.Descriptor instead. func (*ConfigureProviderRefreshResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{119} + return file_openshell_proto_rawDescGZIP(), []int{122} } func (x *ConfigureProviderRefreshResponse) GetStatus() *ProviderCredentialRefreshStatus { @@ -10227,7 +10436,7 @@ type RotateProviderCredentialRequest struct { func (x *RotateProviderCredentialRequest) Reset() { *x = RotateProviderCredentialRequest{} - mi := &file_openshell_proto_msgTypes[120] + mi := &file_openshell_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10239,7 +10448,7 @@ func (x *RotateProviderCredentialRequest) String() string { func (*RotateProviderCredentialRequest) ProtoMessage() {} func (x *RotateProviderCredentialRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[120] + mi := &file_openshell_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10252,7 +10461,7 @@ func (x *RotateProviderCredentialRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RotateProviderCredentialRequest.ProtoReflect.Descriptor instead. func (*RotateProviderCredentialRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{120} + return file_openshell_proto_rawDescGZIP(), []int{123} } func (x *RotateProviderCredentialRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10292,7 +10501,7 @@ type RotateProviderCredentialResponse struct { func (x *RotateProviderCredentialResponse) Reset() { *x = RotateProviderCredentialResponse{} - mi := &file_openshell_proto_msgTypes[121] + mi := &file_openshell_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10304,7 +10513,7 @@ func (x *RotateProviderCredentialResponse) String() string { func (*RotateProviderCredentialResponse) ProtoMessage() {} func (x *RotateProviderCredentialResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[121] + mi := &file_openshell_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10317,7 +10526,7 @@ func (x *RotateProviderCredentialResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RotateProviderCredentialResponse.ProtoReflect.Descriptor instead. func (*RotateProviderCredentialResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{121} + return file_openshell_proto_rawDescGZIP(), []int{124} } func (x *RotateProviderCredentialResponse) GetStatus() *ProviderCredentialRefreshStatus { @@ -10343,7 +10552,7 @@ type DeleteProviderRefreshRequest struct { func (x *DeleteProviderRefreshRequest) Reset() { *x = DeleteProviderRefreshRequest{} - mi := &file_openshell_proto_msgTypes[122] + mi := &file_openshell_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10355,7 +10564,7 @@ func (x *DeleteProviderRefreshRequest) String() string { func (*DeleteProviderRefreshRequest) ProtoMessage() {} func (x *DeleteProviderRefreshRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[122] + mi := &file_openshell_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10368,7 +10577,7 @@ func (x *DeleteProviderRefreshRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRefreshRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderRefreshRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{122} + return file_openshell_proto_rawDescGZIP(), []int{125} } func (x *DeleteProviderRefreshRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10415,7 +10624,7 @@ type DeleteProviderRefreshResponse struct { func (x *DeleteProviderRefreshResponse) Reset() { *x = DeleteProviderRefreshResponse{} - mi := &file_openshell_proto_msgTypes[123] + mi := &file_openshell_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10427,7 +10636,7 @@ func (x *DeleteProviderRefreshResponse) String() string { func (*DeleteProviderRefreshResponse) ProtoMessage() {} func (x *DeleteProviderRefreshResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[123] + mi := &file_openshell_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10440,7 +10649,7 @@ func (x *DeleteProviderRefreshResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRefreshResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderRefreshResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{123} + return file_openshell_proto_rawDescGZIP(), []int{126} } func (x *DeleteProviderRefreshResponse) GetOutcome() DeletionOutcome { @@ -10483,7 +10692,7 @@ type ProviderProfile struct { func (x *ProviderProfile) Reset() { *x = ProviderProfile{} - mi := &file_openshell_proto_msgTypes[124] + mi := &file_openshell_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10495,7 +10704,7 @@ func (x *ProviderProfile) String() string { func (*ProviderProfile) ProtoMessage() {} func (x *ProviderProfile) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[124] + mi := &file_openshell_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10508,7 +10717,7 @@ func (x *ProviderProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfile.ProtoReflect.Descriptor instead. func (*ProviderProfile) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{124} + return file_openshell_proto_rawDescGZIP(), []int{127} } func (x *ProviderProfile) GetId() string { @@ -10624,7 +10833,7 @@ type ProviderProfileFile struct { func (x *ProviderProfileFile) Reset() { *x = ProviderProfileFile{} - mi := &file_openshell_proto_msgTypes[125] + mi := &file_openshell_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10636,7 +10845,7 @@ func (x *ProviderProfileFile) String() string { func (*ProviderProfileFile) ProtoMessage() {} func (x *ProviderProfileFile) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[125] + mi := &file_openshell_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10649,7 +10858,7 @@ func (x *ProviderProfileFile) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileFile.ProtoReflect.Descriptor instead. func (*ProviderProfileFile) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{125} + return file_openshell_proto_rawDescGZIP(), []int{128} } func (x *ProviderProfileFile) GetPath() string { @@ -10683,7 +10892,7 @@ type ProviderProfileResponse struct { func (x *ProviderProfileResponse) Reset() { *x = ProviderProfileResponse{} - mi := &file_openshell_proto_msgTypes[126] + mi := &file_openshell_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10695,7 +10904,7 @@ func (x *ProviderProfileResponse) String() string { func (*ProviderProfileResponse) ProtoMessage() {} func (x *ProviderProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[126] + mi := &file_openshell_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10708,7 +10917,7 @@ func (x *ProviderProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileResponse.ProtoReflect.Descriptor instead. func (*ProviderProfileResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{126} + return file_openshell_proto_rawDescGZIP(), []int{129} } func (x *ProviderProfileResponse) GetProfile() *ProviderProfile { @@ -10730,7 +10939,7 @@ type ListProviderProfilesResponse struct { func (x *ListProviderProfilesResponse) Reset() { *x = ListProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[127] + mi := &file_openshell_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10742,7 +10951,7 @@ func (x *ListProviderProfilesResponse) String() string { func (*ListProviderProfilesResponse) ProtoMessage() {} func (x *ListProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[127] + mi := &file_openshell_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10755,7 +10964,7 @@ func (x *ListProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*ListProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{127} + return file_openshell_proto_rawDescGZIP(), []int{130} } func (x *ListProviderProfilesResponse) GetProfiles() []*ProviderProfile { @@ -10787,7 +10996,7 @@ type ImportProviderProfilesRequest struct { func (x *ImportProviderProfilesRequest) Reset() { *x = ImportProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[128] + mi := &file_openshell_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10799,7 +11008,7 @@ func (x *ImportProviderProfilesRequest) String() string { func (*ImportProviderProfilesRequest) ProtoMessage() {} func (x *ImportProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[128] + mi := &file_openshell_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10812,7 +11021,7 @@ func (x *ImportProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*ImportProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{128} + return file_openshell_proto_rawDescGZIP(), []int{131} } func (x *ImportProviderProfilesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10848,7 +11057,7 @@ type ImportProviderProfilesResponse struct { func (x *ImportProviderProfilesResponse) Reset() { *x = ImportProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[129] + mi := &file_openshell_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10860,7 +11069,7 @@ func (x *ImportProviderProfilesResponse) String() string { func (*ImportProviderProfilesResponse) ProtoMessage() {} func (x *ImportProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[129] + mi := &file_openshell_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10873,7 +11082,7 @@ func (x *ImportProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*ImportProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{129} + return file_openshell_proto_rawDescGZIP(), []int{132} } func (x *ImportProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -10919,7 +11128,7 @@ type UpdateProviderProfilesRequest struct { func (x *UpdateProviderProfilesRequest) Reset() { *x = UpdateProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[130] + mi := &file_openshell_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10931,7 +11140,7 @@ func (x *UpdateProviderProfilesRequest) String() string { func (*UpdateProviderProfilesRequest) ProtoMessage() {} func (x *UpdateProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[130] + mi := &file_openshell_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10944,7 +11153,7 @@ func (x *UpdateProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*UpdateProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{130} + return file_openshell_proto_rawDescGZIP(), []int{133} } func (x *UpdateProviderProfilesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10994,7 +11203,7 @@ type UpdateProviderProfilesResponse struct { func (x *UpdateProviderProfilesResponse) Reset() { *x = UpdateProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[131] + mi := &file_openshell_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11006,7 +11215,7 @@ func (x *UpdateProviderProfilesResponse) String() string { func (*UpdateProviderProfilesResponse) ProtoMessage() {} func (x *UpdateProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[131] + mi := &file_openshell_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11019,7 +11228,7 @@ func (x *UpdateProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*UpdateProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{131} + return file_openshell_proto_rawDescGZIP(), []int{134} } func (x *UpdateProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -11055,7 +11264,7 @@ type LintProviderProfilesRequest struct { func (x *LintProviderProfilesRequest) Reset() { *x = LintProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[132] + mi := &file_openshell_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11067,7 +11276,7 @@ func (x *LintProviderProfilesRequest) String() string { func (*LintProviderProfilesRequest) ProtoMessage() {} func (x *LintProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[132] + mi := &file_openshell_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11080,7 +11289,7 @@ func (x *LintProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LintProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*LintProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{132} + return file_openshell_proto_rawDescGZIP(), []int{135} } func (x *LintProviderProfilesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -11111,7 +11320,7 @@ type LintProviderProfilesResponse struct { func (x *LintProviderProfilesResponse) Reset() { *x = LintProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[133] + mi := &file_openshell_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11123,7 +11332,7 @@ func (x *LintProviderProfilesResponse) String() string { func (*LintProviderProfilesResponse) ProtoMessage() {} func (x *LintProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[133] + mi := &file_openshell_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11136,7 +11345,7 @@ func (x *LintProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LintProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*LintProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{133} + return file_openshell_proto_rawDescGZIP(), []int{136} } func (x *LintProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -11170,7 +11379,7 @@ type DeleteProviderResponse struct { func (x *DeleteProviderResponse) Reset() { *x = DeleteProviderResponse{} - mi := &file_openshell_proto_msgTypes[134] + mi := &file_openshell_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11182,7 +11391,7 @@ func (x *DeleteProviderResponse) String() string { func (*DeleteProviderResponse) ProtoMessage() {} func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[134] + mi := &file_openshell_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11195,7 +11404,7 @@ func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{134} + return file_openshell_proto_rawDescGZIP(), []int{137} } func (x *DeleteProviderResponse) GetOutcome() DeletionOutcome { @@ -11221,7 +11430,7 @@ type DeleteProviderProfileRequest struct { func (x *DeleteProviderProfileRequest) Reset() { *x = DeleteProviderProfileRequest{} - mi := &file_openshell_proto_msgTypes[135] + mi := &file_openshell_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11233,7 +11442,7 @@ func (x *DeleteProviderProfileRequest) String() string { func (*DeleteProviderProfileRequest) ProtoMessage() {} func (x *DeleteProviderProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[135] + mi := &file_openshell_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11246,7 +11455,7 @@ func (x *DeleteProviderProfileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderProfileRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderProfileRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{135} + return file_openshell_proto_rawDescGZIP(), []int{138} } func (x *DeleteProviderProfileRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -11287,7 +11496,7 @@ type DeleteProviderProfileResponse struct { func (x *DeleteProviderProfileResponse) Reset() { *x = DeleteProviderProfileResponse{} - mi := &file_openshell_proto_msgTypes[136] + mi := &file_openshell_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11299,7 +11508,7 @@ func (x *DeleteProviderProfileResponse) String() string { func (*DeleteProviderProfileResponse) ProtoMessage() {} func (x *DeleteProviderProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[136] + mi := &file_openshell_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11312,7 +11521,7 @@ func (x *DeleteProviderProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderProfileResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderProfileResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{136} + return file_openshell_proto_rawDescGZIP(), []int{139} } func (x *DeleteProviderProfileResponse) GetOutcome() DeletionOutcome { @@ -11341,7 +11550,7 @@ type GetSandboxProviderEnvironmentRequest struct { func (x *GetSandboxProviderEnvironmentRequest) Reset() { *x = GetSandboxProviderEnvironmentRequest{} - mi := &file_openshell_proto_msgTypes[137] + mi := &file_openshell_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11353,7 +11562,7 @@ func (x *GetSandboxProviderEnvironmentRequest) String() string { func (*GetSandboxProviderEnvironmentRequest) ProtoMessage() {} func (x *GetSandboxProviderEnvironmentRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[137] + mi := &file_openshell_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11366,7 +11575,7 @@ func (x *GetSandboxProviderEnvironmentRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use GetSandboxProviderEnvironmentRequest.ProtoReflect.Descriptor instead. func (*GetSandboxProviderEnvironmentRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{137} + return file_openshell_proto_rawDescGZIP(), []int{140} } func (x *GetSandboxProviderEnvironmentRequest) GetSandboxId() string { @@ -11402,7 +11611,7 @@ type StaticCredentialEndpointBinding struct { func (x *StaticCredentialEndpointBinding) Reset() { *x = StaticCredentialEndpointBinding{} - mi := &file_openshell_proto_msgTypes[138] + mi := &file_openshell_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11414,7 +11623,7 @@ func (x *StaticCredentialEndpointBinding) String() string { func (*StaticCredentialEndpointBinding) ProtoMessage() {} func (x *StaticCredentialEndpointBinding) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[138] + mi := &file_openshell_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11427,7 +11636,7 @@ func (x *StaticCredentialEndpointBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticCredentialEndpointBinding.ProtoReflect.Descriptor instead. func (*StaticCredentialEndpointBinding) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{138} + return file_openshell_proto_rawDescGZIP(), []int{141} } func (x *StaticCredentialEndpointBinding) GetHost() string { @@ -11471,7 +11680,7 @@ type StaticCredentialBinding struct { func (x *StaticCredentialBinding) Reset() { *x = StaticCredentialBinding{} - mi := &file_openshell_proto_msgTypes[139] + mi := &file_openshell_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11483,7 +11692,7 @@ func (x *StaticCredentialBinding) String() string { func (*StaticCredentialBinding) ProtoMessage() {} func (x *StaticCredentialBinding) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[139] + mi := &file_openshell_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11496,7 +11705,7 @@ func (x *StaticCredentialBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticCredentialBinding.ProtoReflect.Descriptor instead. func (*StaticCredentialBinding) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{139} + return file_openshell_proto_rawDescGZIP(), []int{142} } func (x *StaticCredentialBinding) GetEndpoints() []*StaticCredentialEndpointBinding { @@ -11566,7 +11775,7 @@ type GetSandboxProviderEnvironmentResponse struct { func (x *GetSandboxProviderEnvironmentResponse) Reset() { *x = GetSandboxProviderEnvironmentResponse{} - mi := &file_openshell_proto_msgTypes[140] + mi := &file_openshell_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11578,7 +11787,7 @@ func (x *GetSandboxProviderEnvironmentResponse) String() string { func (*GetSandboxProviderEnvironmentResponse) ProtoMessage() {} func (x *GetSandboxProviderEnvironmentResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[140] + mi := &file_openshell_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11591,7 +11800,7 @@ func (x *GetSandboxProviderEnvironmentResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use GetSandboxProviderEnvironmentResponse.ProtoReflect.Descriptor instead. func (*GetSandboxProviderEnvironmentResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{140} + return file_openshell_proto_rawDescGZIP(), []int{143} } func (x *GetSandboxProviderEnvironmentResponse) GetEnvironment() map[string]string { @@ -11700,7 +11909,7 @@ type ProviderEnvironmentValue struct { func (x *ProviderEnvironmentValue) Reset() { *x = ProviderEnvironmentValue{} - mi := &file_openshell_proto_msgTypes[141] + mi := &file_openshell_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11712,7 +11921,7 @@ func (x *ProviderEnvironmentValue) String() string { func (*ProviderEnvironmentValue) ProtoMessage() {} func (x *ProviderEnvironmentValue) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[141] + mi := &file_openshell_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11725,7 +11934,7 @@ func (x *ProviderEnvironmentValue) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderEnvironmentValue.ProtoReflect.Descriptor instead. func (*ProviderEnvironmentValue) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{141} + return file_openshell_proto_rawDescGZIP(), []int{144} } func (x *ProviderEnvironmentValue) GetName() string { @@ -11791,7 +12000,7 @@ type ProviderEnvironmentSnapshot struct { func (x *ProviderEnvironmentSnapshot) Reset() { *x = ProviderEnvironmentSnapshot{} - mi := &file_openshell_proto_msgTypes[142] + mi := &file_openshell_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11803,7 +12012,7 @@ func (x *ProviderEnvironmentSnapshot) String() string { func (*ProviderEnvironmentSnapshot) ProtoMessage() {} func (x *ProviderEnvironmentSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[142] + mi := &file_openshell_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11816,7 +12025,7 @@ func (x *ProviderEnvironmentSnapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderEnvironmentSnapshot.ProtoReflect.Descriptor instead. func (*ProviderEnvironmentSnapshot) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{142} + return file_openshell_proto_rawDescGZIP(), []int{145} } func (x *ProviderEnvironmentSnapshot) GetProviderEnvRevision() uint64 { @@ -11885,7 +12094,7 @@ type ExchangeProviderSubjectTokenRequest struct { func (x *ExchangeProviderSubjectTokenRequest) Reset() { *x = ExchangeProviderSubjectTokenRequest{} - mi := &file_openshell_proto_msgTypes[143] + mi := &file_openshell_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11897,7 +12106,7 @@ func (x *ExchangeProviderSubjectTokenRequest) String() string { func (*ExchangeProviderSubjectTokenRequest) ProtoMessage() {} func (x *ExchangeProviderSubjectTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[143] + mi := &file_openshell_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11910,7 +12119,7 @@ func (x *ExchangeProviderSubjectTokenRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use ExchangeProviderSubjectTokenRequest.ProtoReflect.Descriptor instead. func (*ExchangeProviderSubjectTokenRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{143} + return file_openshell_proto_rawDescGZIP(), []int{146} } func (x *ExchangeProviderSubjectTokenRequest) GetSandboxId() string { @@ -11952,7 +12161,7 @@ type ExchangeProviderSubjectTokenResponse struct { func (x *ExchangeProviderSubjectTokenResponse) Reset() { *x = ExchangeProviderSubjectTokenResponse{} - mi := &file_openshell_proto_msgTypes[144] + mi := &file_openshell_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11964,7 +12173,7 @@ func (x *ExchangeProviderSubjectTokenResponse) String() string { func (*ExchangeProviderSubjectTokenResponse) ProtoMessage() {} func (x *ExchangeProviderSubjectTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[144] + mi := &file_openshell_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11977,7 +12186,7 @@ func (x *ExchangeProviderSubjectTokenResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use ExchangeProviderSubjectTokenResponse.ProtoReflect.Descriptor instead. func (*ExchangeProviderSubjectTokenResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{144} + return file_openshell_proto_rawDescGZIP(), []int{147} } func (x *ExchangeProviderSubjectTokenResponse) GetAccessToken() string { @@ -12051,7 +12260,7 @@ type UpdateConfigRequest struct { func (x *UpdateConfigRequest) Reset() { *x = UpdateConfigRequest{} - mi := &file_openshell_proto_msgTypes[145] + mi := &file_openshell_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12063,7 +12272,7 @@ func (x *UpdateConfigRequest) String() string { func (*UpdateConfigRequest) ProtoMessage() {} func (x *UpdateConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[145] + mi := &file_openshell_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12076,7 +12285,7 @@ func (x *UpdateConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateConfigRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{145} + return file_openshell_proto_rawDescGZIP(), []int{148} } func (x *UpdateConfigRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -12173,7 +12382,7 @@ type PolicyMergeOperation struct { func (x *PolicyMergeOperation) Reset() { *x = PolicyMergeOperation{} - mi := &file_openshell_proto_msgTypes[146] + mi := &file_openshell_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12185,7 +12394,7 @@ func (x *PolicyMergeOperation) String() string { func (*PolicyMergeOperation) ProtoMessage() {} func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[146] + mi := &file_openshell_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12198,7 +12407,7 @@ func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyMergeOperation.ProtoReflect.Descriptor instead. func (*PolicyMergeOperation) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{146} + return file_openshell_proto_rawDescGZIP(), []int{149} } func (x *PolicyMergeOperation) GetOperation() isPolicyMergeOperation_Operation { @@ -12312,7 +12521,7 @@ type AddNetworkRule struct { func (x *AddNetworkRule) Reset() { *x = AddNetworkRule{} - mi := &file_openshell_proto_msgTypes[147] + mi := &file_openshell_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12324,7 +12533,7 @@ func (x *AddNetworkRule) String() string { func (*AddNetworkRule) ProtoMessage() {} func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[147] + mi := &file_openshell_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12337,7 +12546,7 @@ func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { // Deprecated: Use AddNetworkRule.ProtoReflect.Descriptor instead. func (*AddNetworkRule) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{147} + return file_openshell_proto_rawDescGZIP(), []int{150} } func (x *AddNetworkRule) GetRuleName() string { @@ -12365,7 +12574,7 @@ type RemoveNetworkEndpoint struct { func (x *RemoveNetworkEndpoint) Reset() { *x = RemoveNetworkEndpoint{} - mi := &file_openshell_proto_msgTypes[148] + mi := &file_openshell_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12377,7 +12586,7 @@ func (x *RemoveNetworkEndpoint) String() string { func (*RemoveNetworkEndpoint) ProtoMessage() {} func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[148] + mi := &file_openshell_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12390,7 +12599,7 @@ func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkEndpoint.ProtoReflect.Descriptor instead. func (*RemoveNetworkEndpoint) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{148} + return file_openshell_proto_rawDescGZIP(), []int{151} } func (x *RemoveNetworkEndpoint) GetRuleName() string { @@ -12423,7 +12632,7 @@ type RemoveNetworkRule struct { func (x *RemoveNetworkRule) Reset() { *x = RemoveNetworkRule{} - mi := &file_openshell_proto_msgTypes[149] + mi := &file_openshell_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12435,7 +12644,7 @@ func (x *RemoveNetworkRule) String() string { func (*RemoveNetworkRule) ProtoMessage() {} func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[149] + mi := &file_openshell_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12448,7 +12657,7 @@ func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkRule.ProtoReflect.Descriptor instead. func (*RemoveNetworkRule) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{149} + return file_openshell_proto_rawDescGZIP(), []int{152} } func (x *RemoveNetworkRule) GetRuleName() string { @@ -12477,7 +12686,7 @@ type L7RuleTarget struct { func (x *L7RuleTarget) Reset() { *x = L7RuleTarget{} - mi := &file_openshell_proto_msgTypes[150] + mi := &file_openshell_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12489,7 +12698,7 @@ func (x *L7RuleTarget) String() string { func (*L7RuleTarget) ProtoMessage() {} func (x *L7RuleTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[150] + mi := &file_openshell_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12502,7 +12711,7 @@ func (x *L7RuleTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use L7RuleTarget.ProtoReflect.Descriptor instead. func (*L7RuleTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{150} + return file_openshell_proto_rawDescGZIP(), []int{153} } func (x *L7RuleTarget) GetRuleName() string { @@ -12557,7 +12766,7 @@ type AddDenyRules struct { func (x *AddDenyRules) Reset() { *x = AddDenyRules{} - mi := &file_openshell_proto_msgTypes[151] + mi := &file_openshell_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12569,7 +12778,7 @@ func (x *AddDenyRules) String() string { func (*AddDenyRules) ProtoMessage() {} func (x *AddDenyRules) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[151] + mi := &file_openshell_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12582,7 +12791,7 @@ func (x *AddDenyRules) ProtoReflect() protoreflect.Message { // Deprecated: Use AddDenyRules.ProtoReflect.Descriptor instead. func (*AddDenyRules) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{151} + return file_openshell_proto_rawDescGZIP(), []int{154} } func (x *AddDenyRules) GetDenyRules() []*sandboxv1.L7DenyRule { @@ -12609,7 +12818,7 @@ type AddAllowRules struct { func (x *AddAllowRules) Reset() { *x = AddAllowRules{} - mi := &file_openshell_proto_msgTypes[152] + mi := &file_openshell_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12621,7 +12830,7 @@ func (x *AddAllowRules) String() string { func (*AddAllowRules) ProtoMessage() {} func (x *AddAllowRules) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[152] + mi := &file_openshell_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12634,7 +12843,7 @@ func (x *AddAllowRules) ProtoReflect() protoreflect.Message { // Deprecated: Use AddAllowRules.ProtoReflect.Descriptor instead. func (*AddAllowRules) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{152} + return file_openshell_proto_rawDescGZIP(), []int{155} } func (x *AddAllowRules) GetRules() []*sandboxv1.L7Rule { @@ -12661,7 +12870,7 @@ type RemoveNetworkBinary struct { func (x *RemoveNetworkBinary) Reset() { *x = RemoveNetworkBinary{} - mi := &file_openshell_proto_msgTypes[153] + mi := &file_openshell_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12673,7 +12882,7 @@ func (x *RemoveNetworkBinary) String() string { func (*RemoveNetworkBinary) ProtoMessage() {} func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[153] + mi := &file_openshell_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12686,7 +12895,7 @@ func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkBinary.ProtoReflect.Descriptor instead. func (*RemoveNetworkBinary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{153} + return file_openshell_proto_rawDescGZIP(), []int{156} } func (x *RemoveNetworkBinary) GetRuleName() string { @@ -12722,7 +12931,7 @@ type UpdateConfigResponse struct { func (x *UpdateConfigResponse) Reset() { *x = UpdateConfigResponse{} - mi := &file_openshell_proto_msgTypes[154] + mi := &file_openshell_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12734,7 +12943,7 @@ func (x *UpdateConfigResponse) String() string { func (*UpdateConfigResponse) ProtoMessage() {} func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[154] + mi := &file_openshell_proto_msgTypes[157] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12747,7 +12956,7 @@ func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateConfigResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{154} + return file_openshell_proto_rawDescGZIP(), []int{157} } func (x *UpdateConfigResponse) GetVersion() uint32 { @@ -12801,7 +13010,7 @@ type GetSandboxPolicyStatusRequest struct { func (x *GetSandboxPolicyStatusRequest) Reset() { *x = GetSandboxPolicyStatusRequest{} - mi := &file_openshell_proto_msgTypes[155] + mi := &file_openshell_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12813,7 +13022,7 @@ func (x *GetSandboxPolicyStatusRequest) String() string { func (*GetSandboxPolicyStatusRequest) ProtoMessage() {} func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[155] + mi := &file_openshell_proto_msgTypes[158] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12826,7 +13035,7 @@ func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxPolicyStatusRequest.ProtoReflect.Descriptor instead. func (*GetSandboxPolicyStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{155} + return file_openshell_proto_rawDescGZIP(), []int{158} } func (x *GetSandboxPolicyStatusRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -12870,7 +13079,7 @@ type GetSandboxPolicyStatusResponse struct { func (x *GetSandboxPolicyStatusResponse) Reset() { *x = GetSandboxPolicyStatusResponse{} - mi := &file_openshell_proto_msgTypes[156] + mi := &file_openshell_proto_msgTypes[159] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12882,7 +13091,7 @@ func (x *GetSandboxPolicyStatusResponse) String() string { func (*GetSandboxPolicyStatusResponse) ProtoMessage() {} func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[156] + mi := &file_openshell_proto_msgTypes[159] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12895,7 +13104,7 @@ func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxPolicyStatusResponse.ProtoReflect.Descriptor instead. func (*GetSandboxPolicyStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{156} + return file_openshell_proto_rawDescGZIP(), []int{159} } func (x *GetSandboxPolicyStatusResponse) GetRevision() *SandboxPolicyRevision { @@ -12932,7 +13141,7 @@ type ListSandboxPoliciesRequest struct { func (x *ListSandboxPoliciesRequest) Reset() { *x = ListSandboxPoliciesRequest{} - mi := &file_openshell_proto_msgTypes[157] + mi := &file_openshell_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12944,7 +13153,7 @@ func (x *ListSandboxPoliciesRequest) String() string { func (*ListSandboxPoliciesRequest) ProtoMessage() {} func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[157] + mi := &file_openshell_proto_msgTypes[160] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12957,7 +13166,7 @@ func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxPoliciesRequest.ProtoReflect.Descriptor instead. func (*ListSandboxPoliciesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{157} + return file_openshell_proto_rawDescGZIP(), []int{160} } func (x *ListSandboxPoliciesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -13009,7 +13218,7 @@ type ListSandboxPoliciesResponse struct { func (x *ListSandboxPoliciesResponse) Reset() { *x = ListSandboxPoliciesResponse{} - mi := &file_openshell_proto_msgTypes[158] + mi := &file_openshell_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13021,7 +13230,7 @@ func (x *ListSandboxPoliciesResponse) String() string { func (*ListSandboxPoliciesResponse) ProtoMessage() {} func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[158] + mi := &file_openshell_proto_msgTypes[161] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13034,7 +13243,7 @@ func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxPoliciesResponse.ProtoReflect.Descriptor instead. func (*ListSandboxPoliciesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{158} + return file_openshell_proto_rawDescGZIP(), []int{161} } func (x *ListSandboxPoliciesResponse) GetRevisions() []*SandboxPolicyRevision { @@ -13068,7 +13277,7 @@ type ReportPolicyStatusRequest struct { func (x *ReportPolicyStatusRequest) Reset() { *x = ReportPolicyStatusRequest{} - mi := &file_openshell_proto_msgTypes[159] + mi := &file_openshell_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13080,7 +13289,7 @@ func (x *ReportPolicyStatusRequest) String() string { func (*ReportPolicyStatusRequest) ProtoMessage() {} func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[159] + mi := &file_openshell_proto_msgTypes[162] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13093,7 +13302,7 @@ func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportPolicyStatusRequest.ProtoReflect.Descriptor instead. func (*ReportPolicyStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{159} + return file_openshell_proto_rawDescGZIP(), []int{162} } func (x *ReportPolicyStatusRequest) GetSandboxId() string { @@ -13133,7 +13342,7 @@ type ReportPolicyStatusResponse struct { func (x *ReportPolicyStatusResponse) Reset() { *x = ReportPolicyStatusResponse{} - mi := &file_openshell_proto_msgTypes[160] + mi := &file_openshell_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13145,7 +13354,7 @@ func (x *ReportPolicyStatusResponse) String() string { func (*ReportPolicyStatusResponse) ProtoMessage() {} func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[160] + mi := &file_openshell_proto_msgTypes[163] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13158,7 +13367,7 @@ func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportPolicyStatusResponse.ProtoReflect.Descriptor instead. func (*ReportPolicyStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{160} + return file_openshell_proto_rawDescGZIP(), []int{163} } type SandboxConfigurationAdmission struct { @@ -13176,7 +13385,7 @@ type SandboxConfigurationAdmission struct { func (x *SandboxConfigurationAdmission) Reset() { *x = SandboxConfigurationAdmission{} - mi := &file_openshell_proto_msgTypes[161] + mi := &file_openshell_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13188,7 +13397,7 @@ func (x *SandboxConfigurationAdmission) String() string { func (*SandboxConfigurationAdmission) ProtoMessage() {} func (x *SandboxConfigurationAdmission) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[161] + mi := &file_openshell_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13201,7 +13410,7 @@ func (x *SandboxConfigurationAdmission) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxConfigurationAdmission.ProtoReflect.Descriptor instead. func (*SandboxConfigurationAdmission) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{161} + return file_openshell_proto_rawDescGZIP(), []int{164} } func (x *SandboxConfigurationAdmission) GetInstanceId() string { @@ -13265,7 +13474,7 @@ type ReportSandboxConfigurationRequest struct { func (x *ReportSandboxConfigurationRequest) Reset() { *x = ReportSandboxConfigurationRequest{} - mi := &file_openshell_proto_msgTypes[162] + mi := &file_openshell_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13277,7 +13486,7 @@ func (x *ReportSandboxConfigurationRequest) String() string { func (*ReportSandboxConfigurationRequest) ProtoMessage() {} func (x *ReportSandboxConfigurationRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[162] + mi := &file_openshell_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13290,7 +13499,7 @@ func (x *ReportSandboxConfigurationRequest) ProtoReflect() protoreflect.Message // Deprecated: Use ReportSandboxConfigurationRequest.ProtoReflect.Descriptor instead. func (*ReportSandboxConfigurationRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{162} + return file_openshell_proto_rawDescGZIP(), []int{165} } func (x *ReportSandboxConfigurationRequest) GetSandboxId() string { @@ -13322,7 +13531,7 @@ type ReportSandboxConfigurationResponse struct { func (x *ReportSandboxConfigurationResponse) Reset() { *x = ReportSandboxConfigurationResponse{} - mi := &file_openshell_proto_msgTypes[163] + mi := &file_openshell_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13334,7 +13543,7 @@ func (x *ReportSandboxConfigurationResponse) String() string { func (*ReportSandboxConfigurationResponse) ProtoMessage() {} func (x *ReportSandboxConfigurationResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[163] + mi := &file_openshell_proto_msgTypes[166] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13347,7 +13556,7 @@ func (x *ReportSandboxConfigurationResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ReportSandboxConfigurationResponse.ProtoReflect.Descriptor instead. func (*ReportSandboxConfigurationResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{163} + return file_openshell_proto_rawDescGZIP(), []int{166} } // A versioned policy revision with metadata. @@ -13380,7 +13589,7 @@ type SandboxPolicyRevision struct { func (x *SandboxPolicyRevision) Reset() { *x = SandboxPolicyRevision{} - mi := &file_openshell_proto_msgTypes[164] + mi := &file_openshell_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13392,7 +13601,7 @@ func (x *SandboxPolicyRevision) String() string { func (*SandboxPolicyRevision) ProtoMessage() {} func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[164] + mi := &file_openshell_proto_msgTypes[167] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13405,7 +13614,7 @@ func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxPolicyRevision.ProtoReflect.Descriptor instead. func (*SandboxPolicyRevision) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{164} + return file_openshell_proto_rawDescGZIP(), []int{167} } func (x *SandboxPolicyRevision) GetVersion() uint32 { @@ -13485,7 +13694,7 @@ type GetSandboxLogsRequest struct { func (x *GetSandboxLogsRequest) Reset() { *x = GetSandboxLogsRequest{} - mi := &file_openshell_proto_msgTypes[165] + mi := &file_openshell_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13497,7 +13706,7 @@ func (x *GetSandboxLogsRequest) String() string { func (*GetSandboxLogsRequest) ProtoMessage() {} func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[165] + mi := &file_openshell_proto_msgTypes[168] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13510,7 +13719,7 @@ func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxLogsRequest.ProtoReflect.Descriptor instead. func (*GetSandboxLogsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{165} + return file_openshell_proto_rawDescGZIP(), []int{168} } func (x *GetSandboxLogsRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -13568,7 +13777,7 @@ type PushSandboxLogsRequest struct { func (x *PushSandboxLogsRequest) Reset() { *x = PushSandboxLogsRequest{} - mi := &file_openshell_proto_msgTypes[166] + mi := &file_openshell_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13580,7 +13789,7 @@ func (x *PushSandboxLogsRequest) String() string { func (*PushSandboxLogsRequest) ProtoMessage() {} func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[166] + mi := &file_openshell_proto_msgTypes[169] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13593,7 +13802,7 @@ func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PushSandboxLogsRequest.ProtoReflect.Descriptor instead. func (*PushSandboxLogsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{166} + return file_openshell_proto_rawDescGZIP(), []int{169} } func (x *PushSandboxLogsRequest) GetSandboxId() string { @@ -13619,7 +13828,7 @@ type PushSandboxLogsResponse struct { func (x *PushSandboxLogsResponse) Reset() { *x = PushSandboxLogsResponse{} - mi := &file_openshell_proto_msgTypes[167] + mi := &file_openshell_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13631,7 +13840,7 @@ func (x *PushSandboxLogsResponse) String() string { func (*PushSandboxLogsResponse) ProtoMessage() {} func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[167] + mi := &file_openshell_proto_msgTypes[170] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13644,7 +13853,7 @@ func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PushSandboxLogsResponse.ProtoReflect.Descriptor instead. func (*PushSandboxLogsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{167} + return file_openshell_proto_rawDescGZIP(), []int{170} } // Get sandbox logs response. @@ -13660,7 +13869,7 @@ type GetSandboxLogsResponse struct { func (x *GetSandboxLogsResponse) Reset() { *x = GetSandboxLogsResponse{} - mi := &file_openshell_proto_msgTypes[168] + mi := &file_openshell_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13672,7 +13881,7 @@ func (x *GetSandboxLogsResponse) String() string { func (*GetSandboxLogsResponse) ProtoMessage() {} func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[168] + mi := &file_openshell_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13685,7 +13894,7 @@ func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxLogsResponse.ProtoReflect.Descriptor instead. func (*GetSandboxLogsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{168} + return file_openshell_proto_rawDescGZIP(), []int{171} } func (x *GetSandboxLogsResponse) GetLogs() []*SandboxLogLine { @@ -13719,7 +13928,7 @@ type SupervisorMessage struct { func (x *SupervisorMessage) Reset() { *x = SupervisorMessage{} - mi := &file_openshell_proto_msgTypes[169] + mi := &file_openshell_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13731,7 +13940,7 @@ func (x *SupervisorMessage) String() string { func (*SupervisorMessage) ProtoMessage() {} func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[169] + mi := &file_openshell_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13744,7 +13953,7 @@ func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorMessage.ProtoReflect.Descriptor instead. func (*SupervisorMessage) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{169} + return file_openshell_proto_rawDescGZIP(), []int{172} } func (x *SupervisorMessage) GetPayload() isSupervisorMessage_Payload { @@ -13852,7 +14061,7 @@ type GatewayMessage struct { func (x *GatewayMessage) Reset() { *x = GatewayMessage{} - mi := &file_openshell_proto_msgTypes[170] + mi := &file_openshell_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13864,7 +14073,7 @@ func (x *GatewayMessage) String() string { func (*GatewayMessage) ProtoMessage() {} func (x *GatewayMessage) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[170] + mi := &file_openshell_proto_msgTypes[173] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13877,7 +14086,7 @@ func (x *GatewayMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayMessage.ProtoReflect.Descriptor instead. func (*GatewayMessage) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{170} + return file_openshell_proto_rawDescGZIP(), []int{173} } func (x *GatewayMessage) GetPayload() isGatewayMessage_Payload { @@ -14027,7 +14236,7 @@ type SupervisorHello struct { func (x *SupervisorHello) Reset() { *x = SupervisorHello{} - mi := &file_openshell_proto_msgTypes[171] + mi := &file_openshell_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14039,7 +14248,7 @@ func (x *SupervisorHello) String() string { func (*SupervisorHello) ProtoMessage() {} func (x *SupervisorHello) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[171] + mi := &file_openshell_proto_msgTypes[174] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14052,7 +14261,7 @@ func (x *SupervisorHello) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorHello.ProtoReflect.Descriptor instead. func (*SupervisorHello) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{171} + return file_openshell_proto_rawDescGZIP(), []int{174} } func (x *SupervisorHello) GetSandboxId() string { @@ -14125,7 +14334,7 @@ type SessionAccepted struct { func (x *SessionAccepted) Reset() { *x = SessionAccepted{} - mi := &file_openshell_proto_msgTypes[172] + mi := &file_openshell_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14137,7 +14346,7 @@ func (x *SessionAccepted) String() string { func (*SessionAccepted) ProtoMessage() {} func (x *SessionAccepted) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[172] + mi := &file_openshell_proto_msgTypes[175] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14150,7 +14359,7 @@ func (x *SessionAccepted) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionAccepted.ProtoReflect.Descriptor instead. func (*SessionAccepted) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{172} + return file_openshell_proto_rawDescGZIP(), []int{175} } func (x *SessionAccepted) GetSessionId() string { @@ -14199,7 +14408,7 @@ type ConfigUpdate struct { func (x *ConfigUpdate) Reset() { *x = ConfigUpdate{} - mi := &file_openshell_proto_msgTypes[173] + mi := &file_openshell_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14211,7 +14420,7 @@ func (x *ConfigUpdate) String() string { func (*ConfigUpdate) ProtoMessage() {} func (x *ConfigUpdate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[173] + mi := &file_openshell_proto_msgTypes[176] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14224,7 +14433,7 @@ func (x *ConfigUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigUpdate.ProtoReflect.Descriptor instead. func (*ConfigUpdate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{173} + return file_openshell_proto_rawDescGZIP(), []int{176} } func (x *ConfigUpdate) GetUpdateId() string { @@ -14261,7 +14470,7 @@ type ConfigApplyFailure struct { func (x *ConfigApplyFailure) Reset() { *x = ConfigApplyFailure{} - mi := &file_openshell_proto_msgTypes[174] + mi := &file_openshell_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14273,7 +14482,7 @@ func (x *ConfigApplyFailure) String() string { func (*ConfigApplyFailure) ProtoMessage() {} func (x *ConfigApplyFailure) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[174] + mi := &file_openshell_proto_msgTypes[177] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14286,7 +14495,7 @@ func (x *ConfigApplyFailure) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigApplyFailure.ProtoReflect.Descriptor instead. func (*ConfigApplyFailure) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{174} + return file_openshell_proto_rawDescGZIP(), []int{177} } func (x *ConfigApplyFailure) GetCode() string { @@ -14330,7 +14539,7 @@ type ConfigUpdateResult struct { func (x *ConfigUpdateResult) Reset() { *x = ConfigUpdateResult{} - mi := &file_openshell_proto_msgTypes[175] + mi := &file_openshell_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14342,7 +14551,7 @@ func (x *ConfigUpdateResult) String() string { func (*ConfigUpdateResult) ProtoMessage() {} func (x *ConfigUpdateResult) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[175] + mi := &file_openshell_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14355,7 +14564,7 @@ func (x *ConfigUpdateResult) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigUpdateResult.ProtoReflect.Descriptor instead. func (*ConfigUpdateResult) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{175} + return file_openshell_proto_rawDescGZIP(), []int{178} } func (x *ConfigUpdateResult) GetUpdateId() string { @@ -14406,7 +14615,7 @@ type PolicyLoadResult struct { func (x *PolicyLoadResult) Reset() { *x = PolicyLoadResult{} - mi := &file_openshell_proto_msgTypes[176] + mi := &file_openshell_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14418,7 +14627,7 @@ func (x *PolicyLoadResult) String() string { func (*PolicyLoadResult) ProtoMessage() {} func (x *PolicyLoadResult) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[176] + mi := &file_openshell_proto_msgTypes[179] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14431,7 +14640,7 @@ func (x *PolicyLoadResult) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyLoadResult.ProtoReflect.Descriptor instead. func (*PolicyLoadResult) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{176} + return file_openshell_proto_rawDescGZIP(), []int{179} } func (x *PolicyLoadResult) GetVersion() uint32 { @@ -14466,7 +14675,7 @@ type SessionRejected struct { func (x *SessionRejected) Reset() { *x = SessionRejected{} - mi := &file_openshell_proto_msgTypes[177] + mi := &file_openshell_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14478,7 +14687,7 @@ func (x *SessionRejected) String() string { func (*SessionRejected) ProtoMessage() {} func (x *SessionRejected) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[177] + mi := &file_openshell_proto_msgTypes[180] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14491,7 +14700,7 @@ func (x *SessionRejected) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionRejected.ProtoReflect.Descriptor instead. func (*SessionRejected) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{177} + return file_openshell_proto_rawDescGZIP(), []int{180} } func (x *SessionRejected) GetReason() string { @@ -14520,7 +14729,7 @@ type SessionRedirect struct { func (x *SessionRedirect) Reset() { *x = SessionRedirect{} - mi := &file_openshell_proto_msgTypes[178] + mi := &file_openshell_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14532,7 +14741,7 @@ func (x *SessionRedirect) String() string { func (*SessionRedirect) ProtoMessage() {} func (x *SessionRedirect) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[178] + mi := &file_openshell_proto_msgTypes[181] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14545,7 +14754,7 @@ func (x *SessionRedirect) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionRedirect.ProtoReflect.Descriptor instead. func (*SessionRedirect) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{178} + return file_openshell_proto_rawDescGZIP(), []int{181} } func (x *SessionRedirect) GetPeerEndpoint() string { @@ -14571,7 +14780,7 @@ type SupervisorHeartbeat struct { func (x *SupervisorHeartbeat) Reset() { *x = SupervisorHeartbeat{} - mi := &file_openshell_proto_msgTypes[179] + mi := &file_openshell_proto_msgTypes[182] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14583,7 +14792,7 @@ func (x *SupervisorHeartbeat) String() string { func (*SupervisorHeartbeat) ProtoMessage() {} func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[179] + mi := &file_openshell_proto_msgTypes[182] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14596,7 +14805,7 @@ func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorHeartbeat.ProtoReflect.Descriptor instead. func (*SupervisorHeartbeat) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{179} + return file_openshell_proto_rawDescGZIP(), []int{182} } // Gateway heartbeat. @@ -14608,7 +14817,7 @@ type GatewayHeartbeat struct { func (x *GatewayHeartbeat) Reset() { *x = GatewayHeartbeat{} - mi := &file_openshell_proto_msgTypes[180] + mi := &file_openshell_proto_msgTypes[183] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14620,7 +14829,7 @@ func (x *GatewayHeartbeat) String() string { func (*GatewayHeartbeat) ProtoMessage() {} func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[180] + mi := &file_openshell_proto_msgTypes[183] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14633,7 +14842,7 @@ func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayHeartbeat.ProtoReflect.Descriptor instead. func (*GatewayHeartbeat) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{180} + return file_openshell_proto_rawDescGZIP(), []int{183} } // Terminal result reported before the supervisor shuts down. A successful RPC @@ -14650,7 +14859,7 @@ type ReportMainProcessExitRequest struct { func (x *ReportMainProcessExitRequest) Reset() { *x = ReportMainProcessExitRequest{} - mi := &file_openshell_proto_msgTypes[181] + mi := &file_openshell_proto_msgTypes[184] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14662,7 +14871,7 @@ func (x *ReportMainProcessExitRequest) String() string { func (*ReportMainProcessExitRequest) ProtoMessage() {} func (x *ReportMainProcessExitRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[181] + mi := &file_openshell_proto_msgTypes[184] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14675,7 +14884,7 @@ func (x *ReportMainProcessExitRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportMainProcessExitRequest.ProtoReflect.Descriptor instead. func (*ReportMainProcessExitRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{181} + return file_openshell_proto_rawDescGZIP(), []int{184} } func (x *ReportMainProcessExitRequest) GetSandboxId() string { @@ -14707,7 +14916,7 @@ type ReportMainProcessExitResponse struct { func (x *ReportMainProcessExitResponse) Reset() { *x = ReportMainProcessExitResponse{} - mi := &file_openshell_proto_msgTypes[182] + mi := &file_openshell_proto_msgTypes[185] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14719,7 +14928,7 @@ func (x *ReportMainProcessExitResponse) String() string { func (*ReportMainProcessExitResponse) ProtoMessage() {} func (x *ReportMainProcessExitResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[182] + mi := &file_openshell_proto_msgTypes[185] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14732,7 +14941,7 @@ func (x *ReportMainProcessExitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportMainProcessExitResponse.ProtoReflect.Descriptor instead. func (*ReportMainProcessExitResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{182} + return file_openshell_proto_rawDescGZIP(), []int{185} } // Terminal-delivery completion reported after all expected foreground SSH @@ -14747,7 +14956,7 @@ type FinalizeMainProcessExitRequest struct { func (x *FinalizeMainProcessExitRequest) Reset() { *x = FinalizeMainProcessExitRequest{} - mi := &file_openshell_proto_msgTypes[183] + mi := &file_openshell_proto_msgTypes[186] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14759,7 +14968,7 @@ func (x *FinalizeMainProcessExitRequest) String() string { func (*FinalizeMainProcessExitRequest) ProtoMessage() {} func (x *FinalizeMainProcessExitRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[183] + mi := &file_openshell_proto_msgTypes[186] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14772,7 +14981,7 @@ func (x *FinalizeMainProcessExitRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizeMainProcessExitRequest.ProtoReflect.Descriptor instead. func (*FinalizeMainProcessExitRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{183} + return file_openshell_proto_rawDescGZIP(), []int{186} } func (x *FinalizeMainProcessExitRequest) GetSandboxId() string { @@ -14797,7 +15006,7 @@ type FinalizeMainProcessExitResponse struct { func (x *FinalizeMainProcessExitResponse) Reset() { *x = FinalizeMainProcessExitResponse{} - mi := &file_openshell_proto_msgTypes[184] + mi := &file_openshell_proto_msgTypes[187] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14809,7 +15018,7 @@ func (x *FinalizeMainProcessExitResponse) String() string { func (*FinalizeMainProcessExitResponse) ProtoMessage() {} func (x *FinalizeMainProcessExitResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[184] + mi := &file_openshell_proto_msgTypes[187] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14822,7 +15031,7 @@ func (x *FinalizeMainProcessExitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizeMainProcessExitResponse.ProtoReflect.Descriptor instead. func (*FinalizeMainProcessExitResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{184} + return file_openshell_proto_rawDescGZIP(), []int{187} } // Gateway requests the supervisor to open a relay channel. @@ -14851,7 +15060,7 @@ type RelayOpen struct { func (x *RelayOpen) Reset() { *x = RelayOpen{} - mi := &file_openshell_proto_msgTypes[185] + mi := &file_openshell_proto_msgTypes[188] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14863,7 +15072,7 @@ func (x *RelayOpen) String() string { func (*RelayOpen) ProtoMessage() {} func (x *RelayOpen) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[185] + mi := &file_openshell_proto_msgTypes[188] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14876,7 +15085,7 @@ func (x *RelayOpen) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpen.ProtoReflect.Descriptor instead. func (*RelayOpen) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{185} + return file_openshell_proto_rawDescGZIP(), []int{188} } func (x *RelayOpen) GetChannelId() string { @@ -14943,7 +15152,7 @@ type SshRelayTarget struct { func (x *SshRelayTarget) Reset() { *x = SshRelayTarget{} - mi := &file_openshell_proto_msgTypes[186] + mi := &file_openshell_proto_msgTypes[189] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14955,7 +15164,7 @@ func (x *SshRelayTarget) String() string { func (*SshRelayTarget) ProtoMessage() {} func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[186] + mi := &file_openshell_proto_msgTypes[189] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14968,7 +15177,7 @@ func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use SshRelayTarget.ProtoReflect.Descriptor instead. func (*SshRelayTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{186} + return file_openshell_proto_rawDescGZIP(), []int{189} } // TCP target dialed by the supervisor from inside the sandbox. @@ -14984,7 +15193,7 @@ type TcpRelayTarget struct { func (x *TcpRelayTarget) Reset() { *x = TcpRelayTarget{} - mi := &file_openshell_proto_msgTypes[187] + mi := &file_openshell_proto_msgTypes[190] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14996,7 +15205,7 @@ func (x *TcpRelayTarget) String() string { func (*TcpRelayTarget) ProtoMessage() {} func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[187] + mi := &file_openshell_proto_msgTypes[190] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15009,7 +15218,7 @@ func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use TcpRelayTarget.ProtoReflect.Descriptor instead. func (*TcpRelayTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{187} + return file_openshell_proto_rawDescGZIP(), []int{190} } func (x *TcpRelayTarget) GetHost() string { @@ -15037,7 +15246,7 @@ type RelayInit struct { func (x *RelayInit) Reset() { *x = RelayInit{} - mi := &file_openshell_proto_msgTypes[188] + mi := &file_openshell_proto_msgTypes[191] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15049,7 +15258,7 @@ func (x *RelayInit) String() string { func (*RelayInit) ProtoMessage() {} func (x *RelayInit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[188] + mi := &file_openshell_proto_msgTypes[191] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15062,7 +15271,7 @@ func (x *RelayInit) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayInit.ProtoReflect.Descriptor instead. func (*RelayInit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{188} + return file_openshell_proto_rawDescGZIP(), []int{191} } func (x *RelayInit) GetChannelId() string { @@ -15089,7 +15298,7 @@ type RelayFrame struct { func (x *RelayFrame) Reset() { *x = RelayFrame{} - mi := &file_openshell_proto_msgTypes[189] + mi := &file_openshell_proto_msgTypes[192] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15101,7 +15310,7 @@ func (x *RelayFrame) String() string { func (*RelayFrame) ProtoMessage() {} func (x *RelayFrame) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[189] + mi := &file_openshell_proto_msgTypes[192] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15114,7 +15323,7 @@ func (x *RelayFrame) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayFrame.ProtoReflect.Descriptor instead. func (*RelayFrame) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{189} + return file_openshell_proto_rawDescGZIP(), []int{192} } func (x *RelayFrame) GetPayload() isRelayFrame_Payload { @@ -15174,7 +15383,7 @@ type PeerRelayInit struct { func (x *PeerRelayInit) Reset() { *x = PeerRelayInit{} - mi := &file_openshell_proto_msgTypes[190] + mi := &file_openshell_proto_msgTypes[193] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15186,7 +15395,7 @@ func (x *PeerRelayInit) String() string { func (*PeerRelayInit) ProtoMessage() {} func (x *PeerRelayInit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[190] + mi := &file_openshell_proto_msgTypes[193] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15199,7 +15408,7 @@ func (x *PeerRelayInit) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerRelayInit.ProtoReflect.Descriptor instead. func (*PeerRelayInit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{190} + return file_openshell_proto_rawDescGZIP(), []int{193} } func (x *PeerRelayInit) GetSandboxId() string { @@ -15237,7 +15446,7 @@ type PeerRelayFrame struct { func (x *PeerRelayFrame) Reset() { *x = PeerRelayFrame{} - mi := &file_openshell_proto_msgTypes[191] + mi := &file_openshell_proto_msgTypes[194] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15249,7 +15458,7 @@ func (x *PeerRelayFrame) String() string { func (*PeerRelayFrame) ProtoMessage() {} func (x *PeerRelayFrame) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[191] + mi := &file_openshell_proto_msgTypes[194] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15262,7 +15471,7 @@ func (x *PeerRelayFrame) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerRelayFrame.ProtoReflect.Descriptor instead. func (*PeerRelayFrame) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{191} + return file_openshell_proto_rawDescGZIP(), []int{194} } func (x *PeerRelayFrame) GetPayload() isPeerRelayFrame_Payload { @@ -15317,7 +15526,7 @@ type PeerConfigSandboxTarget struct { func (x *PeerConfigSandboxTarget) Reset() { *x = PeerConfigSandboxTarget{} - mi := &file_openshell_proto_msgTypes[192] + mi := &file_openshell_proto_msgTypes[195] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15329,7 +15538,7 @@ func (x *PeerConfigSandboxTarget) String() string { func (*PeerConfigSandboxTarget) ProtoMessage() {} func (x *PeerConfigSandboxTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[192] + mi := &file_openshell_proto_msgTypes[195] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15342,7 +15551,7 @@ func (x *PeerConfigSandboxTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerConfigSandboxTarget.ProtoReflect.Descriptor instead. func (*PeerConfigSandboxTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{192} + return file_openshell_proto_rawDescGZIP(), []int{195} } func (x *PeerConfigSandboxTarget) GetSandboxId() string { @@ -15370,7 +15579,7 @@ type PeerConfigProviderTarget struct { func (x *PeerConfigProviderTarget) Reset() { *x = PeerConfigProviderTarget{} - mi := &file_openshell_proto_msgTypes[193] + mi := &file_openshell_proto_msgTypes[196] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15382,7 +15591,7 @@ func (x *PeerConfigProviderTarget) String() string { func (*PeerConfigProviderTarget) ProtoMessage() {} func (x *PeerConfigProviderTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[193] + mi := &file_openshell_proto_msgTypes[196] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15395,7 +15604,7 @@ func (x *PeerConfigProviderTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerConfigProviderTarget.ProtoReflect.Descriptor instead. func (*PeerConfigProviderTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{193} + return file_openshell_proto_rawDescGZIP(), []int{196} } func (x *PeerConfigProviderTarget) GetWorkspace() string { @@ -15427,7 +15636,7 @@ type PeerNotifyConfigUpdateRequest struct { func (x *PeerNotifyConfigUpdateRequest) Reset() { *x = PeerNotifyConfigUpdateRequest{} - mi := &file_openshell_proto_msgTypes[194] + mi := &file_openshell_proto_msgTypes[197] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15439,7 +15648,7 @@ func (x *PeerNotifyConfigUpdateRequest) String() string { func (*PeerNotifyConfigUpdateRequest) ProtoMessage() {} func (x *PeerNotifyConfigUpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[194] + mi := &file_openshell_proto_msgTypes[197] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15452,7 +15661,7 @@ func (x *PeerNotifyConfigUpdateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerNotifyConfigUpdateRequest.ProtoReflect.Descriptor instead. func (*PeerNotifyConfigUpdateRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{194} + return file_openshell_proto_rawDescGZIP(), []int{197} } func (x *PeerNotifyConfigUpdateRequest) GetScope() isPeerNotifyConfigUpdateRequest_Scope { @@ -15536,7 +15745,7 @@ type PeerNotifyConfigUpdateResponse struct { func (x *PeerNotifyConfigUpdateResponse) Reset() { *x = PeerNotifyConfigUpdateResponse{} - mi := &file_openshell_proto_msgTypes[195] + mi := &file_openshell_proto_msgTypes[198] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15548,7 +15757,7 @@ func (x *PeerNotifyConfigUpdateResponse) String() string { func (*PeerNotifyConfigUpdateResponse) ProtoMessage() {} func (x *PeerNotifyConfigUpdateResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[195] + mi := &file_openshell_proto_msgTypes[198] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15561,7 +15770,7 @@ func (x *PeerNotifyConfigUpdateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerNotifyConfigUpdateResponse.ProtoReflect.Descriptor instead. func (*PeerNotifyConfigUpdateResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{195} + return file_openshell_proto_rawDescGZIP(), []int{198} } func (x *PeerNotifyConfigUpdateResponse) GetStaleOwner() bool { @@ -15586,7 +15795,7 @@ type RelayOpenResult struct { func (x *RelayOpenResult) Reset() { *x = RelayOpenResult{} - mi := &file_openshell_proto_msgTypes[196] + mi := &file_openshell_proto_msgTypes[199] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15598,7 +15807,7 @@ func (x *RelayOpenResult) String() string { func (*RelayOpenResult) ProtoMessage() {} func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[196] + mi := &file_openshell_proto_msgTypes[199] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15611,7 +15820,7 @@ func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpenResult.ProtoReflect.Descriptor instead. func (*RelayOpenResult) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{196} + return file_openshell_proto_rawDescGZIP(), []int{199} } func (x *RelayOpenResult) GetChannelId() string { @@ -15648,7 +15857,7 @@ type RelayClose struct { func (x *RelayClose) Reset() { *x = RelayClose{} - mi := &file_openshell_proto_msgTypes[197] + mi := &file_openshell_proto_msgTypes[200] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15660,7 +15869,7 @@ func (x *RelayClose) String() string { func (*RelayClose) ProtoMessage() {} func (x *RelayClose) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[197] + mi := &file_openshell_proto_msgTypes[200] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15673,7 +15882,7 @@ func (x *RelayClose) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayClose.ProtoReflect.Descriptor instead. func (*RelayClose) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{197} + return file_openshell_proto_rawDescGZIP(), []int{200} } func (x *RelayClose) GetChannelId() string { @@ -15707,7 +15916,7 @@ type L7RequestSample struct { func (x *L7RequestSample) Reset() { *x = L7RequestSample{} - mi := &file_openshell_proto_msgTypes[198] + mi := &file_openshell_proto_msgTypes[201] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15719,7 +15928,7 @@ func (x *L7RequestSample) String() string { func (*L7RequestSample) ProtoMessage() {} func (x *L7RequestSample) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[198] + mi := &file_openshell_proto_msgTypes[201] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15732,7 +15941,7 @@ func (x *L7RequestSample) ProtoReflect() protoreflect.Message { // Deprecated: Use L7RequestSample.ProtoReflect.Descriptor instead. func (*L7RequestSample) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{198} + return file_openshell_proto_rawDescGZIP(), []int{201} } func (x *L7RequestSample) GetMethod() string { @@ -15806,7 +16015,7 @@ type DenialSummary struct { func (x *DenialSummary) Reset() { *x = DenialSummary{} - mi := &file_openshell_proto_msgTypes[199] + mi := &file_openshell_proto_msgTypes[202] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15818,7 +16027,7 @@ func (x *DenialSummary) String() string { func (*DenialSummary) ProtoMessage() {} func (x *DenialSummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[199] + mi := &file_openshell_proto_msgTypes[202] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15831,7 +16040,7 @@ func (x *DenialSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialSummary.ProtoReflect.Descriptor instead. func (*DenialSummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{199} + return file_openshell_proto_rawDescGZIP(), []int{202} } func (x *DenialSummary) GetSandboxId() string { @@ -15966,7 +16175,7 @@ type DenialGroupCount struct { func (x *DenialGroupCount) Reset() { *x = DenialGroupCount{} - mi := &file_openshell_proto_msgTypes[200] + mi := &file_openshell_proto_msgTypes[203] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15978,7 +16187,7 @@ func (x *DenialGroupCount) String() string { func (*DenialGroupCount) ProtoMessage() {} func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[200] + mi := &file_openshell_proto_msgTypes[203] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15991,7 +16200,7 @@ func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialGroupCount.ProtoReflect.Descriptor instead. func (*DenialGroupCount) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{200} + return file_openshell_proto_rawDescGZIP(), []int{203} } func (x *DenialGroupCount) GetDenyGroup() string { @@ -16024,7 +16233,7 @@ type NetworkActivitySummary struct { func (x *NetworkActivitySummary) Reset() { *x = NetworkActivitySummary{} - mi := &file_openshell_proto_msgTypes[201] + mi := &file_openshell_proto_msgTypes[204] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16036,7 +16245,7 @@ func (x *NetworkActivitySummary) String() string { func (*NetworkActivitySummary) ProtoMessage() {} func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[201] + mi := &file_openshell_proto_msgTypes[204] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16049,7 +16258,7 @@ func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkActivitySummary.ProtoReflect.Descriptor instead. func (*NetworkActivitySummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{201} + return file_openshell_proto_rawDescGZIP(), []int{204} } func (x *NetworkActivitySummary) GetNetworkActivityCount() uint32 { @@ -16137,7 +16346,7 @@ type PolicyChunk struct { func (x *PolicyChunk) Reset() { *x = PolicyChunk{} - mi := &file_openshell_proto_msgTypes[202] + mi := &file_openshell_proto_msgTypes[205] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16149,7 +16358,7 @@ func (x *PolicyChunk) String() string { func (*PolicyChunk) ProtoMessage() {} func (x *PolicyChunk) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[202] + mi := &file_openshell_proto_msgTypes[205] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16162,7 +16371,7 @@ func (x *PolicyChunk) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyChunk.ProtoReflect.Descriptor instead. func (*PolicyChunk) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{202} + return file_openshell_proto_rawDescGZIP(), []int{205} } func (x *PolicyChunk) GetId() string { @@ -16350,7 +16559,7 @@ type DraftPolicyUpdate struct { func (x *DraftPolicyUpdate) Reset() { *x = DraftPolicyUpdate{} - mi := &file_openshell_proto_msgTypes[203] + mi := &file_openshell_proto_msgTypes[206] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16362,7 +16571,7 @@ func (x *DraftPolicyUpdate) String() string { func (*DraftPolicyUpdate) ProtoMessage() {} func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[203] + mi := &file_openshell_proto_msgTypes[206] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16375,7 +16584,7 @@ func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftPolicyUpdate.ProtoReflect.Descriptor instead. func (*DraftPolicyUpdate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{203} + return file_openshell_proto_rawDescGZIP(), []int{206} } func (x *DraftPolicyUpdate) GetDraftVersion() uint64 { @@ -16434,7 +16643,7 @@ type SubmitPolicyAnalysisRequest struct { func (x *SubmitPolicyAnalysisRequest) Reset() { *x = SubmitPolicyAnalysisRequest{} - mi := &file_openshell_proto_msgTypes[204] + mi := &file_openshell_proto_msgTypes[207] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16446,7 +16655,7 @@ func (x *SubmitPolicyAnalysisRequest) String() string { func (*SubmitPolicyAnalysisRequest) ProtoMessage() {} func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[204] + mi := &file_openshell_proto_msgTypes[207] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16459,7 +16668,7 @@ func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisRequest.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{204} + return file_openshell_proto_rawDescGZIP(), []int{207} } func (x *SubmitPolicyAnalysisRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16522,7 +16731,7 @@ type SubmitPolicyAnalysisResponse struct { func (x *SubmitPolicyAnalysisResponse) Reset() { *x = SubmitPolicyAnalysisResponse{} - mi := &file_openshell_proto_msgTypes[205] + mi := &file_openshell_proto_msgTypes[208] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16534,7 +16743,7 @@ func (x *SubmitPolicyAnalysisResponse) String() string { func (*SubmitPolicyAnalysisResponse) ProtoMessage() {} func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[205] + mi := &file_openshell_proto_msgTypes[208] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16547,7 +16756,7 @@ func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisResponse.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{205} + return file_openshell_proto_rawDescGZIP(), []int{208} } func (x *SubmitPolicyAnalysisResponse) GetAcceptedChunks() uint32 { @@ -16592,7 +16801,7 @@ type GetDraftPolicyRequest struct { func (x *GetDraftPolicyRequest) Reset() { *x = GetDraftPolicyRequest{} - mi := &file_openshell_proto_msgTypes[206] + mi := &file_openshell_proto_msgTypes[209] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16604,7 +16813,7 @@ func (x *GetDraftPolicyRequest) String() string { func (*GetDraftPolicyRequest) ProtoMessage() {} func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[206] + mi := &file_openshell_proto_msgTypes[209] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16617,7 +16826,7 @@ func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyRequest.ProtoReflect.Descriptor instead. func (*GetDraftPolicyRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{206} + return file_openshell_proto_rawDescGZIP(), []int{209} } func (x *GetDraftPolicyRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16657,7 +16866,7 @@ type GetDraftPolicyResponse struct { func (x *GetDraftPolicyResponse) Reset() { *x = GetDraftPolicyResponse{} - mi := &file_openshell_proto_msgTypes[207] + mi := &file_openshell_proto_msgTypes[210] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16669,7 +16878,7 @@ func (x *GetDraftPolicyResponse) String() string { func (*GetDraftPolicyResponse) ProtoMessage() {} func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[207] + mi := &file_openshell_proto_msgTypes[210] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16682,7 +16891,7 @@ func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyResponse.ProtoReflect.Descriptor instead. func (*GetDraftPolicyResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{207} + return file_openshell_proto_rawDescGZIP(), []int{210} } func (x *GetDraftPolicyResponse) GetChunks() []*PolicyChunk { @@ -16733,7 +16942,7 @@ type ApproveDraftChunkRequest struct { func (x *ApproveDraftChunkRequest) Reset() { *x = ApproveDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[208] + mi := &file_openshell_proto_msgTypes[211] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16745,7 +16954,7 @@ func (x *ApproveDraftChunkRequest) String() string { func (*ApproveDraftChunkRequest) ProtoMessage() {} func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[208] + mi := &file_openshell_proto_msgTypes[211] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16758,7 +16967,7 @@ func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkRequest.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{208} + return file_openshell_proto_rawDescGZIP(), []int{211} } func (x *ApproveDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16808,7 +17017,7 @@ type ApproveDraftChunkResponse struct { func (x *ApproveDraftChunkResponse) Reset() { *x = ApproveDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[209] + mi := &file_openshell_proto_msgTypes[212] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16820,7 +17029,7 @@ func (x *ApproveDraftChunkResponse) String() string { func (*ApproveDraftChunkResponse) ProtoMessage() {} func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[209] + mi := &file_openshell_proto_msgTypes[212] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16833,7 +17042,7 @@ func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkResponse.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{209} + return file_openshell_proto_rawDescGZIP(), []int{212} } func (x *ApproveDraftChunkResponse) GetPolicyVersion() uint32 { @@ -16869,7 +17078,7 @@ type RejectDraftChunkRequest struct { func (x *RejectDraftChunkRequest) Reset() { *x = RejectDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[210] + mi := &file_openshell_proto_msgTypes[213] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16881,7 +17090,7 @@ func (x *RejectDraftChunkRequest) String() string { func (*RejectDraftChunkRequest) ProtoMessage() {} func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[210] + mi := &file_openshell_proto_msgTypes[213] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16894,7 +17103,7 @@ func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkRequest.ProtoReflect.Descriptor instead. func (*RejectDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{210} + return file_openshell_proto_rawDescGZIP(), []int{213} } func (x *RejectDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16940,7 +17149,7 @@ type RejectDraftChunkResponse struct { func (x *RejectDraftChunkResponse) Reset() { *x = RejectDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[211] + mi := &file_openshell_proto_msgTypes[214] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16952,7 +17161,7 @@ func (x *RejectDraftChunkResponse) String() string { func (*RejectDraftChunkResponse) ProtoMessage() {} func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[211] + mi := &file_openshell_proto_msgTypes[214] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16965,7 +17174,7 @@ func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkResponse.ProtoReflect.Descriptor instead. func (*RejectDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{211} + return file_openshell_proto_rawDescGZIP(), []int{214} } // Approve all pending chunks. @@ -16979,7 +17188,7 @@ type DraftChunkApproval struct { func (x *DraftChunkApproval) Reset() { *x = DraftChunkApproval{} - mi := &file_openshell_proto_msgTypes[212] + mi := &file_openshell_proto_msgTypes[215] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16991,7 +17200,7 @@ func (x *DraftChunkApproval) String() string { func (*DraftChunkApproval) ProtoMessage() {} func (x *DraftChunkApproval) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[212] + mi := &file_openshell_proto_msgTypes[215] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17004,7 +17213,7 @@ func (x *DraftChunkApproval) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftChunkApproval.ProtoReflect.Descriptor instead. func (*DraftChunkApproval) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{212} + return file_openshell_proto_rawDescGZIP(), []int{215} } func (x *DraftChunkApproval) GetChunkId() string { @@ -17040,7 +17249,7 @@ type ApproveAllDraftChunksRequest struct { func (x *ApproveAllDraftChunksRequest) Reset() { *x = ApproveAllDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[213] + mi := &file_openshell_proto_msgTypes[216] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17052,7 +17261,7 @@ func (x *ApproveAllDraftChunksRequest) String() string { func (*ApproveAllDraftChunksRequest) ProtoMessage() {} func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[213] + mi := &file_openshell_proto_msgTypes[216] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17065,7 +17274,7 @@ func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{213} + return file_openshell_proto_rawDescGZIP(), []int{216} } func (x *ApproveAllDraftChunksRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -17120,7 +17329,7 @@ type ApproveAllDraftChunksResponse struct { func (x *ApproveAllDraftChunksResponse) Reset() { *x = ApproveAllDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[214] + mi := &file_openshell_proto_msgTypes[217] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17132,7 +17341,7 @@ func (x *ApproveAllDraftChunksResponse) String() string { func (*ApproveAllDraftChunksResponse) ProtoMessage() {} func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[214] + mi := &file_openshell_proto_msgTypes[217] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17145,7 +17354,7 @@ func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{214} + return file_openshell_proto_rawDescGZIP(), []int{217} } func (x *ApproveAllDraftChunksResponse) GetPolicyVersion() uint32 { @@ -17195,7 +17404,7 @@ type EditDraftChunkRequest struct { func (x *EditDraftChunkRequest) Reset() { *x = EditDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[215] + mi := &file_openshell_proto_msgTypes[218] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17207,7 +17416,7 @@ func (x *EditDraftChunkRequest) String() string { func (*EditDraftChunkRequest) ProtoMessage() {} func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[215] + mi := &file_openshell_proto_msgTypes[218] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17220,7 +17429,7 @@ func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkRequest.ProtoReflect.Descriptor instead. func (*EditDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{215} + return file_openshell_proto_rawDescGZIP(), []int{218} } func (x *EditDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -17266,7 +17475,7 @@ type EditDraftChunkResponse struct { func (x *EditDraftChunkResponse) Reset() { *x = EditDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[216] + mi := &file_openshell_proto_msgTypes[219] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17278,7 +17487,7 @@ func (x *EditDraftChunkResponse) String() string { func (*EditDraftChunkResponse) ProtoMessage() {} func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[216] + mi := &file_openshell_proto_msgTypes[219] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17291,7 +17500,7 @@ func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkResponse.ProtoReflect.Descriptor instead. func (*EditDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{216} + return file_openshell_proto_rawDescGZIP(), []int{219} } // Reverse an approval (remove merged rule from active policy). @@ -17311,7 +17520,7 @@ type UndoDraftChunkRequest struct { func (x *UndoDraftChunkRequest) Reset() { *x = UndoDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[217] + mi := &file_openshell_proto_msgTypes[220] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17323,7 +17532,7 @@ func (x *UndoDraftChunkRequest) String() string { func (*UndoDraftChunkRequest) ProtoMessage() {} func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[217] + mi := &file_openshell_proto_msgTypes[220] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17336,7 +17545,7 @@ func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkRequest.ProtoReflect.Descriptor instead. func (*UndoDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{217} + return file_openshell_proto_rawDescGZIP(), []int{220} } func (x *UndoDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -17379,7 +17588,7 @@ type UndoDraftChunkResponse struct { func (x *UndoDraftChunkResponse) Reset() { *x = UndoDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[218] + mi := &file_openshell_proto_msgTypes[221] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17391,7 +17600,7 @@ func (x *UndoDraftChunkResponse) String() string { func (*UndoDraftChunkResponse) ProtoMessage() {} func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[218] + mi := &file_openshell_proto_msgTypes[221] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17404,7 +17613,7 @@ func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkResponse.ProtoReflect.Descriptor instead. func (*UndoDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{218} + return file_openshell_proto_rawDescGZIP(), []int{221} } func (x *UndoDraftChunkResponse) GetPolicyVersion() uint32 { @@ -17436,7 +17645,7 @@ type ClearDraftChunksRequest struct { func (x *ClearDraftChunksRequest) Reset() { *x = ClearDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[219] + mi := &file_openshell_proto_msgTypes[222] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17448,7 +17657,7 @@ func (x *ClearDraftChunksRequest) String() string { func (*ClearDraftChunksRequest) ProtoMessage() {} func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[219] + mi := &file_openshell_proto_msgTypes[222] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17461,7 +17670,7 @@ func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ClearDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{219} + return file_openshell_proto_rawDescGZIP(), []int{222} } func (x *ClearDraftChunksRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -17495,7 +17704,7 @@ type ClearDraftChunksResponse struct { func (x *ClearDraftChunksResponse) Reset() { *x = ClearDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[220] + mi := &file_openshell_proto_msgTypes[223] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17507,7 +17716,7 @@ func (x *ClearDraftChunksResponse) String() string { func (*ClearDraftChunksResponse) ProtoMessage() {} func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[220] + mi := &file_openshell_proto_msgTypes[223] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17520,7 +17729,7 @@ func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ClearDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{220} + return file_openshell_proto_rawDescGZIP(), []int{223} } func (x *ClearDraftChunksResponse) GetChunksCleared() uint32 { @@ -17542,7 +17751,7 @@ type GetDraftHistoryRequest struct { func (x *GetDraftHistoryRequest) Reset() { *x = GetDraftHistoryRequest{} - mi := &file_openshell_proto_msgTypes[221] + mi := &file_openshell_proto_msgTypes[224] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17554,7 +17763,7 @@ func (x *GetDraftHistoryRequest) String() string { func (*GetDraftHistoryRequest) ProtoMessage() {} func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[221] + mi := &file_openshell_proto_msgTypes[224] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17567,7 +17776,7 @@ func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryRequest.ProtoReflect.Descriptor instead. func (*GetDraftHistoryRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{221} + return file_openshell_proto_rawDescGZIP(), []int{224} } func (x *GetDraftHistoryRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -17601,7 +17810,7 @@ type DraftHistoryEntry struct { func (x *DraftHistoryEntry) Reset() { *x = DraftHistoryEntry{} - mi := &file_openshell_proto_msgTypes[222] + mi := &file_openshell_proto_msgTypes[225] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17613,7 +17822,7 @@ func (x *DraftHistoryEntry) String() string { func (*DraftHistoryEntry) ProtoMessage() {} func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[222] + mi := &file_openshell_proto_msgTypes[225] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17626,7 +17835,7 @@ func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftHistoryEntry.ProtoReflect.Descriptor instead. func (*DraftHistoryEntry) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{222} + return file_openshell_proto_rawDescGZIP(), []int{225} } func (x *DraftHistoryEntry) GetEventTime() *timestamppb.Timestamp { @@ -17667,7 +17876,7 @@ type GetDraftHistoryResponse struct { func (x *GetDraftHistoryResponse) Reset() { *x = GetDraftHistoryResponse{} - mi := &file_openshell_proto_msgTypes[223] + mi := &file_openshell_proto_msgTypes[226] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17679,7 +17888,7 @@ func (x *GetDraftHistoryResponse) String() string { func (*GetDraftHistoryResponse) ProtoMessage() {} func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[223] + mi := &file_openshell_proto_msgTypes[226] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17692,7 +17901,7 @@ func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryResponse.ProtoReflect.Descriptor instead. func (*GetDraftHistoryResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{223} + return file_openshell_proto_rawDescGZIP(), []int{226} } func (x *GetDraftHistoryResponse) GetEntries() []*DraftHistoryEntry { @@ -17717,7 +17926,7 @@ type CreateWorkspaceRequest struct { func (x *CreateWorkspaceRequest) Reset() { *x = CreateWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[224] + mi := &file_openshell_proto_msgTypes[227] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17729,7 +17938,7 @@ func (x *CreateWorkspaceRequest) String() string { func (*CreateWorkspaceRequest) ProtoMessage() {} func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[224] + mi := &file_openshell_proto_msgTypes[227] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17742,7 +17951,7 @@ func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceRequest.ProtoReflect.Descriptor instead. func (*CreateWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{224} + return file_openshell_proto_rawDescGZIP(), []int{227} } func (x *CreateWorkspaceRequest) GetName() string { @@ -17776,7 +17985,7 @@ type CreateWorkspaceResponse struct { func (x *CreateWorkspaceResponse) Reset() { *x = CreateWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[225] + mi := &file_openshell_proto_msgTypes[228] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17788,7 +17997,7 @@ func (x *CreateWorkspaceResponse) String() string { func (*CreateWorkspaceResponse) ProtoMessage() {} func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[225] + mi := &file_openshell_proto_msgTypes[228] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17801,7 +18010,7 @@ func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceResponse.ProtoReflect.Descriptor instead. func (*CreateWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{225} + return file_openshell_proto_rawDescGZIP(), []int{228} } func (x *CreateWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -17822,7 +18031,7 @@ type GetWorkspaceRequest struct { func (x *GetWorkspaceRequest) Reset() { *x = GetWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[226] + mi := &file_openshell_proto_msgTypes[229] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17834,7 +18043,7 @@ func (x *GetWorkspaceRequest) String() string { func (*GetWorkspaceRequest) ProtoMessage() {} func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[226] + mi := &file_openshell_proto_msgTypes[229] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17847,7 +18056,7 @@ func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceRequest.ProtoReflect.Descriptor instead. func (*GetWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{226} + return file_openshell_proto_rawDescGZIP(), []int{229} } func (x *GetWorkspaceRequest) GetName() string { @@ -17867,7 +18076,7 @@ type GetWorkspaceResponse struct { func (x *GetWorkspaceResponse) Reset() { *x = GetWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[227] + mi := &file_openshell_proto_msgTypes[230] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17879,7 +18088,7 @@ func (x *GetWorkspaceResponse) String() string { func (*GetWorkspaceResponse) ProtoMessage() {} func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[227] + mi := &file_openshell_proto_msgTypes[230] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17892,7 +18101,7 @@ func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceResponse.ProtoReflect.Descriptor instead. func (*GetWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{227} + return file_openshell_proto_rawDescGZIP(), []int{230} } func (x *GetWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -17919,7 +18128,7 @@ type ListWorkspacesRequest struct { func (x *ListWorkspacesRequest) Reset() { *x = ListWorkspacesRequest{} - mi := &file_openshell_proto_msgTypes[228] + mi := &file_openshell_proto_msgTypes[231] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17931,7 +18140,7 @@ func (x *ListWorkspacesRequest) String() string { func (*ListWorkspacesRequest) ProtoMessage() {} func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[228] + mi := &file_openshell_proto_msgTypes[231] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17944,7 +18153,7 @@ func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesRequest.ProtoReflect.Descriptor instead. func (*ListWorkspacesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{228} + return file_openshell_proto_rawDescGZIP(), []int{231} } func (x *ListWorkspacesRequest) GetPageSize() int32 { @@ -17980,7 +18189,7 @@ type ListWorkspacesResponse struct { func (x *ListWorkspacesResponse) Reset() { *x = ListWorkspacesResponse{} - mi := &file_openshell_proto_msgTypes[229] + mi := &file_openshell_proto_msgTypes[232] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17992,7 +18201,7 @@ func (x *ListWorkspacesResponse) String() string { func (*ListWorkspacesResponse) ProtoMessage() {} func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[229] + mi := &file_openshell_proto_msgTypes[232] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18005,7 +18214,7 @@ func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesResponse.ProtoReflect.Descriptor instead. func (*ListWorkspacesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{229} + return file_openshell_proto_rawDescGZIP(), []int{232} } func (x *ListWorkspacesResponse) GetWorkspaces() []*datamodelv1.Workspace { @@ -18036,7 +18245,7 @@ type DeleteWorkspaceRequest struct { func (x *DeleteWorkspaceRequest) Reset() { *x = DeleteWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[230] + mi := &file_openshell_proto_msgTypes[233] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18048,7 +18257,7 @@ func (x *DeleteWorkspaceRequest) String() string { func (*DeleteWorkspaceRequest) ProtoMessage() {} func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[230] + mi := &file_openshell_proto_msgTypes[233] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18061,7 +18270,7 @@ func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceRequest.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{230} + return file_openshell_proto_rawDescGZIP(), []int{233} } func (x *DeleteWorkspaceRequest) GetName() string { @@ -18095,7 +18304,7 @@ type DeleteWorkspaceResponse struct { func (x *DeleteWorkspaceResponse) Reset() { *x = DeleteWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[231] + mi := &file_openshell_proto_msgTypes[234] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18107,7 +18316,7 @@ func (x *DeleteWorkspaceResponse) String() string { func (*DeleteWorkspaceResponse) ProtoMessage() {} func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[231] + mi := &file_openshell_proto_msgTypes[234] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18120,7 +18329,7 @@ func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceResponse.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{231} + return file_openshell_proto_rawDescGZIP(), []int{234} } func (x *DeleteWorkspaceResponse) GetOutcome() DeletionOutcome { @@ -18144,7 +18353,7 @@ type WorkspaceMember struct { func (x *WorkspaceMember) Reset() { *x = WorkspaceMember{} - mi := &file_openshell_proto_msgTypes[232] + mi := &file_openshell_proto_msgTypes[235] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18156,7 +18365,7 @@ func (x *WorkspaceMember) String() string { func (*WorkspaceMember) ProtoMessage() {} func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[232] + mi := &file_openshell_proto_msgTypes[235] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18169,7 +18378,7 @@ func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspaceMember.ProtoReflect.Descriptor instead. func (*WorkspaceMember) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{232} + return file_openshell_proto_rawDescGZIP(), []int{235} } func (x *WorkspaceMember) GetMetadata() *datamodelv1.ObjectMeta { @@ -18210,7 +18419,7 @@ type AddWorkspaceMemberRequest struct { func (x *AddWorkspaceMemberRequest) Reset() { *x = AddWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[233] + mi := &file_openshell_proto_msgTypes[236] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18222,7 +18431,7 @@ func (x *AddWorkspaceMemberRequest) String() string { func (*AddWorkspaceMemberRequest) ProtoMessage() {} func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[233] + mi := &file_openshell_proto_msgTypes[236] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18235,7 +18444,7 @@ func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{233} + return file_openshell_proto_rawDescGZIP(), []int{236} } func (x *AddWorkspaceMemberRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -18276,7 +18485,7 @@ type AddWorkspaceMemberResponse struct { func (x *AddWorkspaceMemberResponse) Reset() { *x = AddWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[234] + mi := &file_openshell_proto_msgTypes[237] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18288,7 +18497,7 @@ func (x *AddWorkspaceMemberResponse) String() string { func (*AddWorkspaceMemberResponse) ProtoMessage() {} func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[234] + mi := &file_openshell_proto_msgTypes[237] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18301,7 +18510,7 @@ func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{234} + return file_openshell_proto_rawDescGZIP(), []int{237} } func (x *AddWorkspaceMemberResponse) GetMember() *WorkspaceMember { @@ -18327,7 +18536,7 @@ type RemoveWorkspaceMemberRequest struct { func (x *RemoveWorkspaceMemberRequest) Reset() { *x = RemoveWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[235] + mi := &file_openshell_proto_msgTypes[238] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18339,7 +18548,7 @@ func (x *RemoveWorkspaceMemberRequest) String() string { func (*RemoveWorkspaceMemberRequest) ProtoMessage() {} func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[235] + mi := &file_openshell_proto_msgTypes[238] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18352,7 +18561,7 @@ func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{235} + return file_openshell_proto_rawDescGZIP(), []int{238} } func (x *RemoveWorkspaceMemberRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -18393,7 +18602,7 @@ type RemoveWorkspaceMemberResponse struct { func (x *RemoveWorkspaceMemberResponse) Reset() { *x = RemoveWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[236] + mi := &file_openshell_proto_msgTypes[239] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18405,7 +18614,7 @@ func (x *RemoveWorkspaceMemberResponse) String() string { func (*RemoveWorkspaceMemberResponse) ProtoMessage() {} func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[236] + mi := &file_openshell_proto_msgTypes[239] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18418,7 +18627,7 @@ func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{236} + return file_openshell_proto_rawDescGZIP(), []int{239} } func (x *RemoveWorkspaceMemberResponse) GetOutcome() DeletionOutcome { @@ -18445,7 +18654,7 @@ type ListWorkspaceMembersRequest struct { func (x *ListWorkspaceMembersRequest) Reset() { *x = ListWorkspaceMembersRequest{} - mi := &file_openshell_proto_msgTypes[237] + mi := &file_openshell_proto_msgTypes[240] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18457,7 +18666,7 @@ func (x *ListWorkspaceMembersRequest) String() string { func (*ListWorkspaceMembersRequest) ProtoMessage() {} func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[237] + mi := &file_openshell_proto_msgTypes[240] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18470,7 +18679,7 @@ func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersRequest.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{237} + return file_openshell_proto_rawDescGZIP(), []int{240} } func (x *ListWorkspaceMembersRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -18506,7 +18715,7 @@ type ListWorkspaceMembersResponse struct { func (x *ListWorkspaceMembersResponse) Reset() { *x = ListWorkspaceMembersResponse{} - mi := &file_openshell_proto_msgTypes[238] + mi := &file_openshell_proto_msgTypes[241] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18518,7 +18727,7 @@ func (x *ListWorkspaceMembersResponse) String() string { func (*ListWorkspaceMembersResponse) ProtoMessage() {} func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[238] + mi := &file_openshell_proto_msgTypes[241] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18531,7 +18740,7 @@ func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersResponse.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{238} + return file_openshell_proto_rawDescGZIP(), []int{241} } func (x *ListWorkspaceMembersResponse) GetMembers() []*WorkspaceMember { @@ -18566,7 +18775,7 @@ type ExtensionServiceCredential struct { func (x *ExtensionServiceCredential) Reset() { *x = ExtensionServiceCredential{} - mi := &file_openshell_proto_msgTypes[239] + mi := &file_openshell_proto_msgTypes[242] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18578,7 +18787,7 @@ func (x *ExtensionServiceCredential) String() string { func (*ExtensionServiceCredential) ProtoMessage() {} func (x *ExtensionServiceCredential) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[239] + mi := &file_openshell_proto_msgTypes[242] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18591,7 +18800,7 @@ func (x *ExtensionServiceCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use ExtensionServiceCredential.ProtoReflect.Descriptor instead. func (*ExtensionServiceCredential) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{239} + return file_openshell_proto_rawDescGZIP(), []int{242} } func (x *ExtensionServiceCredential) GetServiceName() string { @@ -18631,7 +18840,7 @@ type EndpointObservation struct { func (x *EndpointObservation) Reset() { *x = EndpointObservation{} - mi := &file_openshell_proto_msgTypes[240] + mi := &file_openshell_proto_msgTypes[243] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18643,7 +18852,7 @@ func (x *EndpointObservation) String() string { func (*EndpointObservation) ProtoMessage() {} func (x *EndpointObservation) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[240] + mi := &file_openshell_proto_msgTypes[243] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18656,7 +18865,7 @@ func (x *EndpointObservation) ProtoReflect() protoreflect.Message { // Deprecated: Use EndpointObservation.ProtoReflect.Descriptor instead. func (*EndpointObservation) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{240} + return file_openshell_proto_rawDescGZIP(), []int{243} } func (x *EndpointObservation) GetEndpointId() string { @@ -18709,7 +18918,7 @@ type ReportEndpointStatusRequest struct { func (x *ReportEndpointStatusRequest) Reset() { *x = ReportEndpointStatusRequest{} - mi := &file_openshell_proto_msgTypes[241] + mi := &file_openshell_proto_msgTypes[244] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18721,7 +18930,7 @@ func (x *ReportEndpointStatusRequest) String() string { func (*ReportEndpointStatusRequest) ProtoMessage() {} func (x *ReportEndpointStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[241] + mi := &file_openshell_proto_msgTypes[244] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18734,7 +18943,7 @@ func (x *ReportEndpointStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportEndpointStatusRequest.ProtoReflect.Descriptor instead. func (*ReportEndpointStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{241} + return file_openshell_proto_rawDescGZIP(), []int{244} } func (x *ReportEndpointStatusRequest) GetSandboxId() string { @@ -18803,7 +19012,7 @@ type TokenGrantStatus struct { func (x *TokenGrantStatus) Reset() { *x = TokenGrantStatus{} - mi := &file_openshell_proto_msgTypes[242] + mi := &file_openshell_proto_msgTypes[245] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18815,7 +19024,7 @@ func (x *TokenGrantStatus) String() string { func (*TokenGrantStatus) ProtoMessage() {} func (x *TokenGrantStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[242] + mi := &file_openshell_proto_msgTypes[245] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18828,7 +19037,7 @@ func (x *TokenGrantStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use TokenGrantStatus.ProtoReflect.Descriptor instead. func (*TokenGrantStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{242} + return file_openshell_proto_rawDescGZIP(), []int{245} } func (x *TokenGrantStatus) GetLastFailure() *TokenGrantFailureObservation { @@ -18855,7 +19064,7 @@ type TokenGrantFailureObservation struct { func (x *TokenGrantFailureObservation) Reset() { *x = TokenGrantFailureObservation{} - mi := &file_openshell_proto_msgTypes[243] + mi := &file_openshell_proto_msgTypes[246] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18867,7 +19076,7 @@ func (x *TokenGrantFailureObservation) String() string { func (*TokenGrantFailureObservation) ProtoMessage() {} func (x *TokenGrantFailureObservation) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[243] + mi := &file_openshell_proto_msgTypes[246] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18880,7 +19089,7 @@ func (x *TokenGrantFailureObservation) ProtoReflect() protoreflect.Message { // Deprecated: Use TokenGrantFailureObservation.ProtoReflect.Descriptor instead. func (*TokenGrantFailureObservation) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{243} + return file_openshell_proto_rawDescGZIP(), []int{246} } func (x *TokenGrantFailureObservation) GetDestinationHost() string { @@ -18913,7 +19122,7 @@ type ReportEndpointStatusResponse struct { func (x *ReportEndpointStatusResponse) Reset() { *x = ReportEndpointStatusResponse{} - mi := &file_openshell_proto_msgTypes[244] + mi := &file_openshell_proto_msgTypes[247] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18925,7 +19134,7 @@ func (x *ReportEndpointStatusResponse) String() string { func (*ReportEndpointStatusResponse) ProtoMessage() {} func (x *ReportEndpointStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[244] + mi := &file_openshell_proto_msgTypes[247] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18938,7 +19147,7 @@ func (x *ReportEndpointStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportEndpointStatusResponse.ProtoReflect.Descriptor instead. func (*ReportEndpointStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{244} + return file_openshell_proto_rawDescGZIP(), []int{247} } // A configured endpoint and its last accepted network result in one record. @@ -18972,7 +19181,7 @@ type EndpointStatus struct { func (x *EndpointStatus) Reset() { *x = EndpointStatus{} - mi := &file_openshell_proto_msgTypes[245] + mi := &file_openshell_proto_msgTypes[248] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18984,7 +19193,7 @@ func (x *EndpointStatus) String() string { func (*EndpointStatus) ProtoMessage() {} func (x *EndpointStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[245] + mi := &file_openshell_proto_msgTypes[248] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18997,7 +19206,7 @@ func (x *EndpointStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use EndpointStatus.ProtoReflect.Descriptor instead. func (*EndpointStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{245} + return file_openshell_proto_rawDescGZIP(), []int{248} } func (x *EndpointStatus) GetEndpointId() string { @@ -19092,7 +19301,7 @@ type SandboxProvisioning struct { func (x *SandboxProvisioning) Reset() { *x = SandboxProvisioning{} - mi := &file_openshell_proto_msgTypes[246] + mi := &file_openshell_proto_msgTypes[249] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19104,7 +19313,7 @@ func (x *SandboxProvisioning) String() string { func (*SandboxProvisioning) ProtoMessage() {} func (x *SandboxProvisioning) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[246] + mi := &file_openshell_proto_msgTypes[249] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19117,7 +19326,7 @@ func (x *SandboxProvisioning) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxProvisioning.ProtoReflect.Descriptor instead. func (*SandboxProvisioning) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{246} + return file_openshell_proto_rawDescGZIP(), []int{249} } func (x *SandboxProvisioning) GetAttemptId() string { @@ -19240,7 +19449,7 @@ type SandboxServiceExposure struct { func (x *SandboxServiceExposure) Reset() { *x = SandboxServiceExposure{} - mi := &file_openshell_proto_msgTypes[247] + mi := &file_openshell_proto_msgTypes[250] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19252,7 +19461,7 @@ func (x *SandboxServiceExposure) String() string { func (*SandboxServiceExposure) ProtoMessage() {} func (x *SandboxServiceExposure) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[247] + mi := &file_openshell_proto_msgTypes[250] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19265,7 +19474,7 @@ func (x *SandboxServiceExposure) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxServiceExposure.ProtoReflect.Descriptor instead. func (*SandboxServiceExposure) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{247} + return file_openshell_proto_rawDescGZIP(), []int{250} } func (x *SandboxServiceExposure) GetService() string { @@ -19319,7 +19528,7 @@ type EndpointFailure struct { func (x *EndpointFailure) Reset() { *x = EndpointFailure{} - mi := &file_openshell_proto_msgTypes[248] + mi := &file_openshell_proto_msgTypes[251] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -19331,7 +19540,7 @@ func (x *EndpointFailure) String() string { func (*EndpointFailure) ProtoMessage() {} func (x *EndpointFailure) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[248] + mi := &file_openshell_proto_msgTypes[251] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -19344,7 +19553,7 @@ func (x *EndpointFailure) ProtoReflect() protoreflect.Message { // Deprecated: Use EndpointFailure.ProtoReflect.Descriptor instead. func (*EndpointFailure) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{248} + return file_openshell_proto_rawDescGZIP(), []int{251} } func (x *EndpointFailure) GetDomain() EndpointFailureDomain { @@ -19602,6 +19811,25 @@ const file_openshell_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a>\n" + "\x10AnnotationsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xf8\x03\n" + + "\x14UpdateSandboxRequest\x12R\n" + + "\x0fworkspace_scope\x18\x01 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x19\n" + + "\x05image\x18\x03 \x01(\tH\x00R\x05image\x88\x01\x01\x12<\n" + + "\acommand\x18\x06 \x01(\v2\".openshell.v1.SandboxCommandUpdateR\acommand\x12@\n" + + "\venvironment\x18\a \x01(\v2\x1e.openshell.v1.SandboxMapUpdateR\venvironment\x126\n" + + "\x06labels\x18\b \x01(\v2\x1e.openshell.v1.SandboxMapUpdateR\x06labels\x12@\n" + + "\vannotations\x18\t \x01(\v2\x1e.openshell.v1.SandboxMapUpdateR\vannotations\x12:\n" + + "\x19expected_resource_version\x18\x04 \x01(\x04R\x17expectedResourceVersion\x12\x1d\n" + + "\n" + + "request_id\x18\x05 \x01(\tR\trequestIdB\b\n" + + "\x06_image\"*\n" + + "\x14SandboxCommandUpdate\x12\x12\n" + + "\x04argv\x18\x01 \x03(\tR\x04argv\"\x91\x01\n" + + "\x10SandboxMapUpdate\x12B\n" + + "\x06values\x18\x01 \x03(\v2*.openshell.v1.SandboxMapUpdate.ValuesEntryR\x06values\x1a9\n" + + "\vValuesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd4\x01\n" + "\x1cCreateSandboxTemplateRequest\x12R\n" + "\x0fworkspace_scope\x18\x02 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x12A\n" + @@ -21186,7 +21414,10 @@ const file_openshell_proto_rawDesc = "" + "\x15RemoveWorkspaceMember\x12*.openshell.v1.RemoveWorkspaceMemberRequest\x1a+.openshell.v1.RemoveWorkspaceMemberResponse\"$\x82\xb5\x18 \n" + "\x06bearer\x12\x05admin\"\x0fworkspace:write\x12\x91\x01\n" + "\x14ListWorkspaceMembers\x12).openshell.v1.ListWorkspaceMembersRequest\x1a*.openshell.v1.ListWorkspaceMembersResponse\"\"\x82\xb5\x18\x1e\n" + - "\x06bearer\x12\x04user\"\x0eworkspace:readb\x06proto3" + "\x06bearer\x12\x04user\"\x0eworkspace:read2\x87\x01\n" + + "\x0eSandboxUpdates\x12u\n" + + "\rUpdateSandbox\x12\".openshell.v1.UpdateSandboxRequest\x1a\x1d.openshell.v1.SandboxResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:writeb\x06proto3" var ( file_openshell_proto_rawDescOnce sync.Once @@ -21201,7 +21432,7 @@ func file_openshell_proto_rawDescGZIP() []byte { } var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 22) -var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 275) +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 @@ -21258,274 +21489,278 @@ var file_openshell_proto_goTypes = []any{ (*SandboxCondition)(nil), // 52: openshell.v1.SandboxCondition (*PlatformEvent)(nil), // 53: openshell.v1.PlatformEvent (*CreateSandboxRequest)(nil), // 54: openshell.v1.CreateSandboxRequest - (*CreateSandboxTemplateRequest)(nil), // 55: openshell.v1.CreateSandboxTemplateRequest - (*GetSandboxTemplateRequest)(nil), // 56: openshell.v1.GetSandboxTemplateRequest - (*ListSandboxTemplatesRequest)(nil), // 57: openshell.v1.ListSandboxTemplatesRequest - (*DeleteSandboxTemplateRequest)(nil), // 58: openshell.v1.DeleteSandboxTemplateRequest - (*SandboxTemplateResponse)(nil), // 59: openshell.v1.SandboxTemplateResponse - (*ListSandboxTemplatesResponse)(nil), // 60: openshell.v1.ListSandboxTemplatesResponse - (*DeleteSandboxTemplateResponse)(nil), // 61: openshell.v1.DeleteSandboxTemplateResponse - (*BeginRootfsTarStagingRequest)(nil), // 62: openshell.v1.BeginRootfsTarStagingRequest - (*BeginRootfsTarStagingResponse)(nil), // 63: openshell.v1.BeginRootfsTarStagingResponse - (*GetSandboxRequest)(nil), // 64: openshell.v1.GetSandboxRequest - (*ListSandboxesRequest)(nil), // 65: openshell.v1.ListSandboxesRequest - (*ListSandboxProvidersRequest)(nil), // 66: openshell.v1.ListSandboxProvidersRequest - (*AttachSandboxProviderRequest)(nil), // 67: openshell.v1.AttachSandboxProviderRequest - (*DetachSandboxProviderRequest)(nil), // 68: openshell.v1.DetachSandboxProviderRequest - (*DeleteSandboxRequest)(nil), // 69: openshell.v1.DeleteSandboxRequest - (*StopSandboxRequest)(nil), // 70: openshell.v1.StopSandboxRequest - (*StartSandboxRequest)(nil), // 71: openshell.v1.StartSandboxRequest - (*SandboxResponse)(nil), // 72: openshell.v1.SandboxResponse - (*ListSandboxesResponse)(nil), // 73: openshell.v1.ListSandboxesResponse - (*ListSandboxProvidersResponse)(nil), // 74: openshell.v1.ListSandboxProvidersResponse - (*AttachSandboxProviderResponse)(nil), // 75: openshell.v1.AttachSandboxProviderResponse - (*DetachSandboxProviderResponse)(nil), // 76: openshell.v1.DetachSandboxProviderResponse - (*ProviderDesiredIdentity)(nil), // 77: openshell.v1.ProviderDesiredIdentity - (*ConfigSnapshotRevision)(nil), // 78: openshell.v1.ConfigSnapshotRevision - (*SandboxConfigRevision)(nil), // 79: openshell.v1.SandboxConfigRevision - (*ConfigUpdateOperation)(nil), // 80: openshell.v1.ConfigUpdateOperation - (*ProviderMutationReceipt)(nil), // 81: openshell.v1.ProviderMutationReceipt - (*ProviderReadinessObservation)(nil), // 82: openshell.v1.ProviderReadinessObservation - (*ProviderReadinessStatus)(nil), // 83: openshell.v1.ProviderReadinessStatus - (*GetSandboxProviderStatusRequest)(nil), // 84: openshell.v1.GetSandboxProviderStatusRequest - (*GetSandboxProviderStatusResponse)(nil), // 85: openshell.v1.GetSandboxProviderStatusResponse - (*ReportProviderReadinessRequest)(nil), // 86: openshell.v1.ReportProviderReadinessRequest - (*ReportProviderReadinessResponse)(nil), // 87: openshell.v1.ReportProviderReadinessResponse - (*DeleteSandboxResponse)(nil), // 88: openshell.v1.DeleteSandboxResponse - (*CreateSshSessionRequest)(nil), // 89: openshell.v1.CreateSshSessionRequest - (*CreateSshSessionResponse)(nil), // 90: openshell.v1.CreateSshSessionResponse - (*ExposeServiceRequest)(nil), // 91: openshell.v1.ExposeServiceRequest - (*GetServiceRequest)(nil), // 92: openshell.v1.GetServiceRequest - (*ListServicesRequest)(nil), // 93: openshell.v1.ListServicesRequest - (*ListServicesResponse)(nil), // 94: openshell.v1.ListServicesResponse - (*DeleteServiceRequest)(nil), // 95: openshell.v1.DeleteServiceRequest - (*DeleteServiceResponse)(nil), // 96: openshell.v1.DeleteServiceResponse - (*ServiceEndpoint)(nil), // 97: openshell.v1.ServiceEndpoint - (*ServiceEndpointResponse)(nil), // 98: openshell.v1.ServiceEndpointResponse - (*RevokeSshSessionRequest)(nil), // 99: openshell.v1.RevokeSshSessionRequest - (*RevokeSshSessionResponse)(nil), // 100: openshell.v1.RevokeSshSessionResponse - (*ExecSandboxRequest)(nil), // 101: openshell.v1.ExecSandboxRequest - (*ExecSandboxStdout)(nil), // 102: openshell.v1.ExecSandboxStdout - (*ExecSandboxStderr)(nil), // 103: openshell.v1.ExecSandboxStderr - (*ExecSandboxExit)(nil), // 104: openshell.v1.ExecSandboxExit - (*ExecSandboxEvent)(nil), // 105: openshell.v1.ExecSandboxEvent - (*TcpForwardInit)(nil), // 106: openshell.v1.TcpForwardInit - (*TcpForwardFrame)(nil), // 107: openshell.v1.TcpForwardFrame - (*ExecSandboxInput)(nil), // 108: openshell.v1.ExecSandboxInput - (*ExecSandboxWindowResize)(nil), // 109: openshell.v1.ExecSandboxWindowResize - (*SshSession)(nil), // 110: openshell.v1.SshSession - (*WatchSandboxRequest)(nil), // 111: openshell.v1.WatchSandboxRequest - (*SandboxStreamEvent)(nil), // 112: openshell.v1.SandboxStreamEvent - (*SandboxLogLine)(nil), // 113: openshell.v1.SandboxLogLine - (*SandboxStreamWarning)(nil), // 114: openshell.v1.SandboxStreamWarning - (*CreateProviderRequest)(nil), // 115: openshell.v1.CreateProviderRequest - (*GetProviderRequest)(nil), // 116: openshell.v1.GetProviderRequest - (*GetProviderCredentialsRequest)(nil), // 117: openshell.v1.GetProviderCredentialsRequest - (*ProviderCredentialValue)(nil), // 118: openshell.v1.ProviderCredentialValue - (*GetProviderCredentialsResponse)(nil), // 119: openshell.v1.GetProviderCredentialsResponse - (*ListProvidersRequest)(nil), // 120: openshell.v1.ListProvidersRequest - (*UpdateProviderRequest)(nil), // 121: openshell.v1.UpdateProviderRequest - (*DeleteProviderRequest)(nil), // 122: openshell.v1.DeleteProviderRequest - (*ProviderResponse)(nil), // 123: openshell.v1.ProviderResponse - (*ListProvidersResponse)(nil), // 124: openshell.v1.ListProvidersResponse - (*ListProviderProfilesRequest)(nil), // 125: openshell.v1.ListProviderProfilesRequest - (*GetProviderProfileRequest)(nil), // 126: openshell.v1.GetProviderProfileRequest - (*ProviderProfileImportItem)(nil), // 127: openshell.v1.ProviderProfileImportItem - (*ProviderProfileDiagnostic)(nil), // 128: openshell.v1.ProviderProfileDiagnostic - (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 129: openshell.v1.ProviderCredentialTokenGrantAudienceOverride - (*ProviderCredentialTokenGrantSubjectToken)(nil), // 130: openshell.v1.ProviderCredentialTokenGrantSubjectToken - (*ProviderCredentialTokenGrant)(nil), // 131: openshell.v1.ProviderCredentialTokenGrant - (*ProviderProfileCredential)(nil), // 132: openshell.v1.ProviderProfileCredential - (*ProviderCredentialRefreshMaterial)(nil), // 133: openshell.v1.ProviderCredentialRefreshMaterial - (*ProviderCredentialRefreshOutput)(nil), // 134: openshell.v1.ProviderCredentialRefreshOutput - (*ProviderCredentialRefresh)(nil), // 135: openshell.v1.ProviderCredentialRefresh - (*ProviderCredentialRefreshStatus)(nil), // 136: openshell.v1.ProviderCredentialRefreshStatus - (*ProviderProfileDiscovery)(nil), // 137: openshell.v1.ProviderProfileDiscovery - (*GetProviderRefreshStatusRequest)(nil), // 138: openshell.v1.GetProviderRefreshStatusRequest - (*GetProviderRefreshStatusResponse)(nil), // 139: openshell.v1.GetProviderRefreshStatusResponse - (*ConfigureProviderRefreshRequest)(nil), // 140: openshell.v1.ConfigureProviderRefreshRequest - (*ConfigureProviderRefreshResponse)(nil), // 141: openshell.v1.ConfigureProviderRefreshResponse - (*RotateProviderCredentialRequest)(nil), // 142: openshell.v1.RotateProviderCredentialRequest - (*RotateProviderCredentialResponse)(nil), // 143: openshell.v1.RotateProviderCredentialResponse - (*DeleteProviderRefreshRequest)(nil), // 144: openshell.v1.DeleteProviderRefreshRequest - (*DeleteProviderRefreshResponse)(nil), // 145: openshell.v1.DeleteProviderRefreshResponse - (*ProviderProfile)(nil), // 146: openshell.v1.ProviderProfile - (*ProviderProfileFile)(nil), // 147: openshell.v1.ProviderProfileFile - (*ProviderProfileResponse)(nil), // 148: openshell.v1.ProviderProfileResponse - (*ListProviderProfilesResponse)(nil), // 149: openshell.v1.ListProviderProfilesResponse - (*ImportProviderProfilesRequest)(nil), // 150: openshell.v1.ImportProviderProfilesRequest - (*ImportProviderProfilesResponse)(nil), // 151: openshell.v1.ImportProviderProfilesResponse - (*UpdateProviderProfilesRequest)(nil), // 152: openshell.v1.UpdateProviderProfilesRequest - (*UpdateProviderProfilesResponse)(nil), // 153: openshell.v1.UpdateProviderProfilesResponse - (*LintProviderProfilesRequest)(nil), // 154: openshell.v1.LintProviderProfilesRequest - (*LintProviderProfilesResponse)(nil), // 155: openshell.v1.LintProviderProfilesResponse - (*DeleteProviderResponse)(nil), // 156: openshell.v1.DeleteProviderResponse - (*DeleteProviderProfileRequest)(nil), // 157: openshell.v1.DeleteProviderProfileRequest - (*DeleteProviderProfileResponse)(nil), // 158: openshell.v1.DeleteProviderProfileResponse - (*GetSandboxProviderEnvironmentRequest)(nil), // 159: openshell.v1.GetSandboxProviderEnvironmentRequest - (*StaticCredentialEndpointBinding)(nil), // 160: openshell.v1.StaticCredentialEndpointBinding - (*StaticCredentialBinding)(nil), // 161: openshell.v1.StaticCredentialBinding - (*GetSandboxProviderEnvironmentResponse)(nil), // 162: openshell.v1.GetSandboxProviderEnvironmentResponse - (*ProviderEnvironmentValue)(nil), // 163: openshell.v1.ProviderEnvironmentValue - (*ProviderEnvironmentSnapshot)(nil), // 164: openshell.v1.ProviderEnvironmentSnapshot - (*ExchangeProviderSubjectTokenRequest)(nil), // 165: openshell.v1.ExchangeProviderSubjectTokenRequest - (*ExchangeProviderSubjectTokenResponse)(nil), // 166: openshell.v1.ExchangeProviderSubjectTokenResponse - (*UpdateConfigRequest)(nil), // 167: openshell.v1.UpdateConfigRequest - (*PolicyMergeOperation)(nil), // 168: openshell.v1.PolicyMergeOperation - (*AddNetworkRule)(nil), // 169: openshell.v1.AddNetworkRule - (*RemoveNetworkEndpoint)(nil), // 170: openshell.v1.RemoveNetworkEndpoint - (*RemoveNetworkRule)(nil), // 171: openshell.v1.RemoveNetworkRule - (*L7RuleTarget)(nil), // 172: openshell.v1.L7RuleTarget - (*AddDenyRules)(nil), // 173: openshell.v1.AddDenyRules - (*AddAllowRules)(nil), // 174: openshell.v1.AddAllowRules - (*RemoveNetworkBinary)(nil), // 175: openshell.v1.RemoveNetworkBinary - (*UpdateConfigResponse)(nil), // 176: openshell.v1.UpdateConfigResponse - (*GetSandboxPolicyStatusRequest)(nil), // 177: openshell.v1.GetSandboxPolicyStatusRequest - (*GetSandboxPolicyStatusResponse)(nil), // 178: openshell.v1.GetSandboxPolicyStatusResponse - (*ListSandboxPoliciesRequest)(nil), // 179: openshell.v1.ListSandboxPoliciesRequest - (*ListSandboxPoliciesResponse)(nil), // 180: openshell.v1.ListSandboxPoliciesResponse - (*ReportPolicyStatusRequest)(nil), // 181: openshell.v1.ReportPolicyStatusRequest - (*ReportPolicyStatusResponse)(nil), // 182: openshell.v1.ReportPolicyStatusResponse - (*SandboxConfigurationAdmission)(nil), // 183: openshell.v1.SandboxConfigurationAdmission - (*ReportSandboxConfigurationRequest)(nil), // 184: openshell.v1.ReportSandboxConfigurationRequest - (*ReportSandboxConfigurationResponse)(nil), // 185: openshell.v1.ReportSandboxConfigurationResponse - (*SandboxPolicyRevision)(nil), // 186: openshell.v1.SandboxPolicyRevision - (*GetSandboxLogsRequest)(nil), // 187: openshell.v1.GetSandboxLogsRequest - (*PushSandboxLogsRequest)(nil), // 188: openshell.v1.PushSandboxLogsRequest - (*PushSandboxLogsResponse)(nil), // 189: openshell.v1.PushSandboxLogsResponse - (*GetSandboxLogsResponse)(nil), // 190: openshell.v1.GetSandboxLogsResponse - (*SupervisorMessage)(nil), // 191: openshell.v1.SupervisorMessage - (*GatewayMessage)(nil), // 192: openshell.v1.GatewayMessage - (*SupervisorHello)(nil), // 193: openshell.v1.SupervisorHello - (*SessionAccepted)(nil), // 194: openshell.v1.SessionAccepted - (*ConfigUpdate)(nil), // 195: openshell.v1.ConfigUpdate - (*ConfigApplyFailure)(nil), // 196: openshell.v1.ConfigApplyFailure - (*ConfigUpdateResult)(nil), // 197: openshell.v1.ConfigUpdateResult - (*PolicyLoadResult)(nil), // 198: openshell.v1.PolicyLoadResult - (*SessionRejected)(nil), // 199: openshell.v1.SessionRejected - (*SessionRedirect)(nil), // 200: openshell.v1.SessionRedirect - (*SupervisorHeartbeat)(nil), // 201: openshell.v1.SupervisorHeartbeat - (*GatewayHeartbeat)(nil), // 202: openshell.v1.GatewayHeartbeat - (*ReportMainProcessExitRequest)(nil), // 203: openshell.v1.ReportMainProcessExitRequest - (*ReportMainProcessExitResponse)(nil), // 204: openshell.v1.ReportMainProcessExitResponse - (*FinalizeMainProcessExitRequest)(nil), // 205: openshell.v1.FinalizeMainProcessExitRequest - (*FinalizeMainProcessExitResponse)(nil), // 206: openshell.v1.FinalizeMainProcessExitResponse - (*RelayOpen)(nil), // 207: openshell.v1.RelayOpen - (*SshRelayTarget)(nil), // 208: openshell.v1.SshRelayTarget - (*TcpRelayTarget)(nil), // 209: openshell.v1.TcpRelayTarget - (*RelayInit)(nil), // 210: openshell.v1.RelayInit - (*RelayFrame)(nil), // 211: openshell.v1.RelayFrame - (*PeerRelayInit)(nil), // 212: openshell.v1.PeerRelayInit - (*PeerRelayFrame)(nil), // 213: openshell.v1.PeerRelayFrame - (*PeerConfigSandboxTarget)(nil), // 214: openshell.v1.PeerConfigSandboxTarget - (*PeerConfigProviderTarget)(nil), // 215: openshell.v1.PeerConfigProviderTarget - (*PeerNotifyConfigUpdateRequest)(nil), // 216: openshell.v1.PeerNotifyConfigUpdateRequest - (*PeerNotifyConfigUpdateResponse)(nil), // 217: openshell.v1.PeerNotifyConfigUpdateResponse - (*RelayOpenResult)(nil), // 218: openshell.v1.RelayOpenResult - (*RelayClose)(nil), // 219: openshell.v1.RelayClose - (*L7RequestSample)(nil), // 220: openshell.v1.L7RequestSample - (*DenialSummary)(nil), // 221: openshell.v1.DenialSummary - (*DenialGroupCount)(nil), // 222: openshell.v1.DenialGroupCount - (*NetworkActivitySummary)(nil), // 223: openshell.v1.NetworkActivitySummary - (*PolicyChunk)(nil), // 224: openshell.v1.PolicyChunk - (*DraftPolicyUpdate)(nil), // 225: openshell.v1.DraftPolicyUpdate - (*SubmitPolicyAnalysisRequest)(nil), // 226: openshell.v1.SubmitPolicyAnalysisRequest - (*SubmitPolicyAnalysisResponse)(nil), // 227: openshell.v1.SubmitPolicyAnalysisResponse - (*GetDraftPolicyRequest)(nil), // 228: openshell.v1.GetDraftPolicyRequest - (*GetDraftPolicyResponse)(nil), // 229: openshell.v1.GetDraftPolicyResponse - (*ApproveDraftChunkRequest)(nil), // 230: openshell.v1.ApproveDraftChunkRequest - (*ApproveDraftChunkResponse)(nil), // 231: openshell.v1.ApproveDraftChunkResponse - (*RejectDraftChunkRequest)(nil), // 232: openshell.v1.RejectDraftChunkRequest - (*RejectDraftChunkResponse)(nil), // 233: openshell.v1.RejectDraftChunkResponse - (*DraftChunkApproval)(nil), // 234: openshell.v1.DraftChunkApproval - (*ApproveAllDraftChunksRequest)(nil), // 235: openshell.v1.ApproveAllDraftChunksRequest - (*ApproveAllDraftChunksResponse)(nil), // 236: openshell.v1.ApproveAllDraftChunksResponse - (*EditDraftChunkRequest)(nil), // 237: openshell.v1.EditDraftChunkRequest - (*EditDraftChunkResponse)(nil), // 238: openshell.v1.EditDraftChunkResponse - (*UndoDraftChunkRequest)(nil), // 239: openshell.v1.UndoDraftChunkRequest - (*UndoDraftChunkResponse)(nil), // 240: openshell.v1.UndoDraftChunkResponse - (*ClearDraftChunksRequest)(nil), // 241: openshell.v1.ClearDraftChunksRequest - (*ClearDraftChunksResponse)(nil), // 242: openshell.v1.ClearDraftChunksResponse - (*GetDraftHistoryRequest)(nil), // 243: openshell.v1.GetDraftHistoryRequest - (*DraftHistoryEntry)(nil), // 244: openshell.v1.DraftHistoryEntry - (*GetDraftHistoryResponse)(nil), // 245: openshell.v1.GetDraftHistoryResponse - (*CreateWorkspaceRequest)(nil), // 246: openshell.v1.CreateWorkspaceRequest - (*CreateWorkspaceResponse)(nil), // 247: openshell.v1.CreateWorkspaceResponse - (*GetWorkspaceRequest)(nil), // 248: openshell.v1.GetWorkspaceRequest - (*GetWorkspaceResponse)(nil), // 249: openshell.v1.GetWorkspaceResponse - (*ListWorkspacesRequest)(nil), // 250: openshell.v1.ListWorkspacesRequest - (*ListWorkspacesResponse)(nil), // 251: openshell.v1.ListWorkspacesResponse - (*DeleteWorkspaceRequest)(nil), // 252: openshell.v1.DeleteWorkspaceRequest - (*DeleteWorkspaceResponse)(nil), // 253: openshell.v1.DeleteWorkspaceResponse - (*WorkspaceMember)(nil), // 254: openshell.v1.WorkspaceMember - (*AddWorkspaceMemberRequest)(nil), // 255: openshell.v1.AddWorkspaceMemberRequest - (*AddWorkspaceMemberResponse)(nil), // 256: openshell.v1.AddWorkspaceMemberResponse - (*RemoveWorkspaceMemberRequest)(nil), // 257: openshell.v1.RemoveWorkspaceMemberRequest - (*RemoveWorkspaceMemberResponse)(nil), // 258: openshell.v1.RemoveWorkspaceMemberResponse - (*ListWorkspaceMembersRequest)(nil), // 259: openshell.v1.ListWorkspaceMembersRequest - (*ListWorkspaceMembersResponse)(nil), // 260: openshell.v1.ListWorkspaceMembersResponse - (*ExtensionServiceCredential)(nil), // 261: openshell.v1.ExtensionServiceCredential - (*EndpointObservation)(nil), // 262: openshell.v1.EndpointObservation - (*ReportEndpointStatusRequest)(nil), // 263: openshell.v1.ReportEndpointStatusRequest - (*TokenGrantStatus)(nil), // 264: openshell.v1.TokenGrantStatus - (*TokenGrantFailureObservation)(nil), // 265: openshell.v1.TokenGrantFailureObservation - (*ReportEndpointStatusResponse)(nil), // 266: openshell.v1.ReportEndpointStatusResponse - (*EndpointStatus)(nil), // 267: openshell.v1.EndpointStatus - (*SandboxProvisioning)(nil), // 268: openshell.v1.SandboxProvisioning - (*SandboxServiceExposure)(nil), // 269: openshell.v1.SandboxServiceExposure - (*EndpointFailure)(nil), // 270: openshell.v1.EndpointFailure - nil, // 271: openshell.v1.SandboxSpec.EnvironmentEntry - nil, // 272: openshell.v1.SandboxTemplate.LabelsEntry - nil, // 273: openshell.v1.SandboxTemplate.AnnotationsEntry - nil, // 274: openshell.v1.SandboxTemplate.EnvironmentEntry - nil, // 275: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry - nil, // 276: openshell.v1.PlatformEvent.MetadataEntry - nil, // 277: openshell.v1.CreateSandboxRequest.LabelsEntry - nil, // 278: openshell.v1.CreateSandboxRequest.AnnotationsEntry - nil, // 279: openshell.v1.SandboxResponse.ServiceUrlsEntry - nil, // 280: openshell.v1.ExecSandboxRequest.EnvironmentEntry - nil, // 281: openshell.v1.SandboxLogLine.FieldsEntry - nil, // 282: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry - nil, // 283: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - nil, // 284: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - nil, // 285: openshell.v1.ProviderProfile.AnnotationsEntry - nil, // 286: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - nil, // 287: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - nil, // 288: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - nil, // 289: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - nil, // 290: openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry - nil, // 291: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry - nil, // 292: openshell.v1.ProviderEnvironmentSnapshot.FilesEntry - nil, // 293: openshell.v1.UpdateConfigRequest.AnnotationsEntry - nil, // 294: openshell.v1.UpdateConfigResponse.AnnotationsEntry - nil, // 295: openshell.v1.SandboxPolicyRevision.ProvenanceEntry - nil, // 296: openshell.v1.CreateWorkspaceRequest.LabelsEntry - (*timestamppb.Timestamp)(nil), // 297: google.protobuf.Timestamp - (*datamodelv1.ObjectMeta)(nil), // 298: openshell.datamodel.v1.ObjectMeta - (*sandboxv1.SandboxPolicy)(nil), // 299: openshell.sandbox.v1.SandboxPolicy - (*structpb.Struct)(nil), // 300: google.protobuf.Struct - (*durationpb.Duration)(nil), // 301: google.protobuf.Duration - (*datamodelv1.WorkspaceSelector)(nil), // 302: openshell.datamodel.v1.WorkspaceSelector - (*datamodelv1.Provider)(nil), // 303: openshell.datamodel.v1.Provider - (sandboxv1.PolicySource)(0), // 304: openshell.sandbox.v1.PolicySource - (*sandboxv1.NetworkEndpoint)(nil), // 305: openshell.sandbox.v1.NetworkEndpoint - (*sandboxv1.NetworkBinary)(nil), // 306: openshell.sandbox.v1.NetworkBinary - (*sandboxv1.SettingValue)(nil), // 307: openshell.sandbox.v1.SettingValue - (*sandboxv1.NetworkPolicyRule)(nil), // 308: openshell.sandbox.v1.NetworkPolicyRule - (*sandboxv1.L7DenyRule)(nil), // 309: openshell.sandbox.v1.L7DenyRule - (*sandboxv1.L7Rule)(nil), // 310: openshell.sandbox.v1.L7Rule - (*sandboxv1.SandboxConfigSnapshot)(nil), // 311: openshell.sandbox.v1.SandboxConfigSnapshot - (*datamodelv1.Workspace)(nil), // 312: openshell.datamodel.v1.Workspace - (*sandboxv1.GetSandboxConfigRequest)(nil), // 313: openshell.sandbox.v1.GetSandboxConfigRequest - (*sandboxv1.GetGatewayConfigRequest)(nil), // 314: openshell.sandbox.v1.GetGatewayConfigRequest - (*sandboxv1.GetSandboxConfigResponse)(nil), // 315: openshell.sandbox.v1.GetSandboxConfigResponse - (*sandboxv1.GetGatewayConfigResponse)(nil), // 316: openshell.sandbox.v1.GetGatewayConfigResponse + (*UpdateSandboxRequest)(nil), // 55: openshell.v1.UpdateSandboxRequest + (*SandboxCommandUpdate)(nil), // 56: openshell.v1.SandboxCommandUpdate + (*SandboxMapUpdate)(nil), // 57: openshell.v1.SandboxMapUpdate + (*CreateSandboxTemplateRequest)(nil), // 58: openshell.v1.CreateSandboxTemplateRequest + (*GetSandboxTemplateRequest)(nil), // 59: openshell.v1.GetSandboxTemplateRequest + (*ListSandboxTemplatesRequest)(nil), // 60: openshell.v1.ListSandboxTemplatesRequest + (*DeleteSandboxTemplateRequest)(nil), // 61: openshell.v1.DeleteSandboxTemplateRequest + (*SandboxTemplateResponse)(nil), // 62: openshell.v1.SandboxTemplateResponse + (*ListSandboxTemplatesResponse)(nil), // 63: openshell.v1.ListSandboxTemplatesResponse + (*DeleteSandboxTemplateResponse)(nil), // 64: openshell.v1.DeleteSandboxTemplateResponse + (*BeginRootfsTarStagingRequest)(nil), // 65: openshell.v1.BeginRootfsTarStagingRequest + (*BeginRootfsTarStagingResponse)(nil), // 66: openshell.v1.BeginRootfsTarStagingResponse + (*GetSandboxRequest)(nil), // 67: openshell.v1.GetSandboxRequest + (*ListSandboxesRequest)(nil), // 68: openshell.v1.ListSandboxesRequest + (*ListSandboxProvidersRequest)(nil), // 69: openshell.v1.ListSandboxProvidersRequest + (*AttachSandboxProviderRequest)(nil), // 70: openshell.v1.AttachSandboxProviderRequest + (*DetachSandboxProviderRequest)(nil), // 71: openshell.v1.DetachSandboxProviderRequest + (*DeleteSandboxRequest)(nil), // 72: openshell.v1.DeleteSandboxRequest + (*StopSandboxRequest)(nil), // 73: openshell.v1.StopSandboxRequest + (*StartSandboxRequest)(nil), // 74: openshell.v1.StartSandboxRequest + (*SandboxResponse)(nil), // 75: openshell.v1.SandboxResponse + (*ListSandboxesResponse)(nil), // 76: openshell.v1.ListSandboxesResponse + (*ListSandboxProvidersResponse)(nil), // 77: openshell.v1.ListSandboxProvidersResponse + (*AttachSandboxProviderResponse)(nil), // 78: openshell.v1.AttachSandboxProviderResponse + (*DetachSandboxProviderResponse)(nil), // 79: openshell.v1.DetachSandboxProviderResponse + (*ProviderDesiredIdentity)(nil), // 80: openshell.v1.ProviderDesiredIdentity + (*ConfigSnapshotRevision)(nil), // 81: openshell.v1.ConfigSnapshotRevision + (*SandboxConfigRevision)(nil), // 82: openshell.v1.SandboxConfigRevision + (*ConfigUpdateOperation)(nil), // 83: openshell.v1.ConfigUpdateOperation + (*ProviderMutationReceipt)(nil), // 84: openshell.v1.ProviderMutationReceipt + (*ProviderReadinessObservation)(nil), // 85: openshell.v1.ProviderReadinessObservation + (*ProviderReadinessStatus)(nil), // 86: openshell.v1.ProviderReadinessStatus + (*GetSandboxProviderStatusRequest)(nil), // 87: openshell.v1.GetSandboxProviderStatusRequest + (*GetSandboxProviderStatusResponse)(nil), // 88: openshell.v1.GetSandboxProviderStatusResponse + (*ReportProviderReadinessRequest)(nil), // 89: openshell.v1.ReportProviderReadinessRequest + (*ReportProviderReadinessResponse)(nil), // 90: openshell.v1.ReportProviderReadinessResponse + (*DeleteSandboxResponse)(nil), // 91: openshell.v1.DeleteSandboxResponse + (*CreateSshSessionRequest)(nil), // 92: openshell.v1.CreateSshSessionRequest + (*CreateSshSessionResponse)(nil), // 93: openshell.v1.CreateSshSessionResponse + (*ExposeServiceRequest)(nil), // 94: openshell.v1.ExposeServiceRequest + (*GetServiceRequest)(nil), // 95: openshell.v1.GetServiceRequest + (*ListServicesRequest)(nil), // 96: openshell.v1.ListServicesRequest + (*ListServicesResponse)(nil), // 97: openshell.v1.ListServicesResponse + (*DeleteServiceRequest)(nil), // 98: openshell.v1.DeleteServiceRequest + (*DeleteServiceResponse)(nil), // 99: openshell.v1.DeleteServiceResponse + (*ServiceEndpoint)(nil), // 100: openshell.v1.ServiceEndpoint + (*ServiceEndpointResponse)(nil), // 101: openshell.v1.ServiceEndpointResponse + (*RevokeSshSessionRequest)(nil), // 102: openshell.v1.RevokeSshSessionRequest + (*RevokeSshSessionResponse)(nil), // 103: openshell.v1.RevokeSshSessionResponse + (*ExecSandboxRequest)(nil), // 104: openshell.v1.ExecSandboxRequest + (*ExecSandboxStdout)(nil), // 105: openshell.v1.ExecSandboxStdout + (*ExecSandboxStderr)(nil), // 106: openshell.v1.ExecSandboxStderr + (*ExecSandboxExit)(nil), // 107: openshell.v1.ExecSandboxExit + (*ExecSandboxEvent)(nil), // 108: openshell.v1.ExecSandboxEvent + (*TcpForwardInit)(nil), // 109: openshell.v1.TcpForwardInit + (*TcpForwardFrame)(nil), // 110: openshell.v1.TcpForwardFrame + (*ExecSandboxInput)(nil), // 111: openshell.v1.ExecSandboxInput + (*ExecSandboxWindowResize)(nil), // 112: openshell.v1.ExecSandboxWindowResize + (*SshSession)(nil), // 113: openshell.v1.SshSession + (*WatchSandboxRequest)(nil), // 114: openshell.v1.WatchSandboxRequest + (*SandboxStreamEvent)(nil), // 115: openshell.v1.SandboxStreamEvent + (*SandboxLogLine)(nil), // 116: openshell.v1.SandboxLogLine + (*SandboxStreamWarning)(nil), // 117: openshell.v1.SandboxStreamWarning + (*CreateProviderRequest)(nil), // 118: openshell.v1.CreateProviderRequest + (*GetProviderRequest)(nil), // 119: openshell.v1.GetProviderRequest + (*GetProviderCredentialsRequest)(nil), // 120: openshell.v1.GetProviderCredentialsRequest + (*ProviderCredentialValue)(nil), // 121: openshell.v1.ProviderCredentialValue + (*GetProviderCredentialsResponse)(nil), // 122: openshell.v1.GetProviderCredentialsResponse + (*ListProvidersRequest)(nil), // 123: openshell.v1.ListProvidersRequest + (*UpdateProviderRequest)(nil), // 124: openshell.v1.UpdateProviderRequest + (*DeleteProviderRequest)(nil), // 125: openshell.v1.DeleteProviderRequest + (*ProviderResponse)(nil), // 126: openshell.v1.ProviderResponse + (*ListProvidersResponse)(nil), // 127: openshell.v1.ListProvidersResponse + (*ListProviderProfilesRequest)(nil), // 128: openshell.v1.ListProviderProfilesRequest + (*GetProviderProfileRequest)(nil), // 129: openshell.v1.GetProviderProfileRequest + (*ProviderProfileImportItem)(nil), // 130: openshell.v1.ProviderProfileImportItem + (*ProviderProfileDiagnostic)(nil), // 131: openshell.v1.ProviderProfileDiagnostic + (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 132: openshell.v1.ProviderCredentialTokenGrantAudienceOverride + (*ProviderCredentialTokenGrantSubjectToken)(nil), // 133: openshell.v1.ProviderCredentialTokenGrantSubjectToken + (*ProviderCredentialTokenGrant)(nil), // 134: openshell.v1.ProviderCredentialTokenGrant + (*ProviderProfileCredential)(nil), // 135: openshell.v1.ProviderProfileCredential + (*ProviderCredentialRefreshMaterial)(nil), // 136: openshell.v1.ProviderCredentialRefreshMaterial + (*ProviderCredentialRefreshOutput)(nil), // 137: openshell.v1.ProviderCredentialRefreshOutput + (*ProviderCredentialRefresh)(nil), // 138: openshell.v1.ProviderCredentialRefresh + (*ProviderCredentialRefreshStatus)(nil), // 139: openshell.v1.ProviderCredentialRefreshStatus + (*ProviderProfileDiscovery)(nil), // 140: openshell.v1.ProviderProfileDiscovery + (*GetProviderRefreshStatusRequest)(nil), // 141: openshell.v1.GetProviderRefreshStatusRequest + (*GetProviderRefreshStatusResponse)(nil), // 142: openshell.v1.GetProviderRefreshStatusResponse + (*ConfigureProviderRefreshRequest)(nil), // 143: openshell.v1.ConfigureProviderRefreshRequest + (*ConfigureProviderRefreshResponse)(nil), // 144: openshell.v1.ConfigureProviderRefreshResponse + (*RotateProviderCredentialRequest)(nil), // 145: openshell.v1.RotateProviderCredentialRequest + (*RotateProviderCredentialResponse)(nil), // 146: openshell.v1.RotateProviderCredentialResponse + (*DeleteProviderRefreshRequest)(nil), // 147: openshell.v1.DeleteProviderRefreshRequest + (*DeleteProviderRefreshResponse)(nil), // 148: openshell.v1.DeleteProviderRefreshResponse + (*ProviderProfile)(nil), // 149: openshell.v1.ProviderProfile + (*ProviderProfileFile)(nil), // 150: openshell.v1.ProviderProfileFile + (*ProviderProfileResponse)(nil), // 151: openshell.v1.ProviderProfileResponse + (*ListProviderProfilesResponse)(nil), // 152: openshell.v1.ListProviderProfilesResponse + (*ImportProviderProfilesRequest)(nil), // 153: openshell.v1.ImportProviderProfilesRequest + (*ImportProviderProfilesResponse)(nil), // 154: openshell.v1.ImportProviderProfilesResponse + (*UpdateProviderProfilesRequest)(nil), // 155: openshell.v1.UpdateProviderProfilesRequest + (*UpdateProviderProfilesResponse)(nil), // 156: openshell.v1.UpdateProviderProfilesResponse + (*LintProviderProfilesRequest)(nil), // 157: openshell.v1.LintProviderProfilesRequest + (*LintProviderProfilesResponse)(nil), // 158: openshell.v1.LintProviderProfilesResponse + (*DeleteProviderResponse)(nil), // 159: openshell.v1.DeleteProviderResponse + (*DeleteProviderProfileRequest)(nil), // 160: openshell.v1.DeleteProviderProfileRequest + (*DeleteProviderProfileResponse)(nil), // 161: openshell.v1.DeleteProviderProfileResponse + (*GetSandboxProviderEnvironmentRequest)(nil), // 162: openshell.v1.GetSandboxProviderEnvironmentRequest + (*StaticCredentialEndpointBinding)(nil), // 163: openshell.v1.StaticCredentialEndpointBinding + (*StaticCredentialBinding)(nil), // 164: openshell.v1.StaticCredentialBinding + (*GetSandboxProviderEnvironmentResponse)(nil), // 165: openshell.v1.GetSandboxProviderEnvironmentResponse + (*ProviderEnvironmentValue)(nil), // 166: openshell.v1.ProviderEnvironmentValue + (*ProviderEnvironmentSnapshot)(nil), // 167: openshell.v1.ProviderEnvironmentSnapshot + (*ExchangeProviderSubjectTokenRequest)(nil), // 168: openshell.v1.ExchangeProviderSubjectTokenRequest + (*ExchangeProviderSubjectTokenResponse)(nil), // 169: openshell.v1.ExchangeProviderSubjectTokenResponse + (*UpdateConfigRequest)(nil), // 170: openshell.v1.UpdateConfigRequest + (*PolicyMergeOperation)(nil), // 171: openshell.v1.PolicyMergeOperation + (*AddNetworkRule)(nil), // 172: openshell.v1.AddNetworkRule + (*RemoveNetworkEndpoint)(nil), // 173: openshell.v1.RemoveNetworkEndpoint + (*RemoveNetworkRule)(nil), // 174: openshell.v1.RemoveNetworkRule + (*L7RuleTarget)(nil), // 175: openshell.v1.L7RuleTarget + (*AddDenyRules)(nil), // 176: openshell.v1.AddDenyRules + (*AddAllowRules)(nil), // 177: openshell.v1.AddAllowRules + (*RemoveNetworkBinary)(nil), // 178: openshell.v1.RemoveNetworkBinary + (*UpdateConfigResponse)(nil), // 179: openshell.v1.UpdateConfigResponse + (*GetSandboxPolicyStatusRequest)(nil), // 180: openshell.v1.GetSandboxPolicyStatusRequest + (*GetSandboxPolicyStatusResponse)(nil), // 181: openshell.v1.GetSandboxPolicyStatusResponse + (*ListSandboxPoliciesRequest)(nil), // 182: openshell.v1.ListSandboxPoliciesRequest + (*ListSandboxPoliciesResponse)(nil), // 183: openshell.v1.ListSandboxPoliciesResponse + (*ReportPolicyStatusRequest)(nil), // 184: openshell.v1.ReportPolicyStatusRequest + (*ReportPolicyStatusResponse)(nil), // 185: openshell.v1.ReportPolicyStatusResponse + (*SandboxConfigurationAdmission)(nil), // 186: openshell.v1.SandboxConfigurationAdmission + (*ReportSandboxConfigurationRequest)(nil), // 187: openshell.v1.ReportSandboxConfigurationRequest + (*ReportSandboxConfigurationResponse)(nil), // 188: openshell.v1.ReportSandboxConfigurationResponse + (*SandboxPolicyRevision)(nil), // 189: openshell.v1.SandboxPolicyRevision + (*GetSandboxLogsRequest)(nil), // 190: openshell.v1.GetSandboxLogsRequest + (*PushSandboxLogsRequest)(nil), // 191: openshell.v1.PushSandboxLogsRequest + (*PushSandboxLogsResponse)(nil), // 192: openshell.v1.PushSandboxLogsResponse + (*GetSandboxLogsResponse)(nil), // 193: openshell.v1.GetSandboxLogsResponse + (*SupervisorMessage)(nil), // 194: openshell.v1.SupervisorMessage + (*GatewayMessage)(nil), // 195: openshell.v1.GatewayMessage + (*SupervisorHello)(nil), // 196: openshell.v1.SupervisorHello + (*SessionAccepted)(nil), // 197: openshell.v1.SessionAccepted + (*ConfigUpdate)(nil), // 198: openshell.v1.ConfigUpdate + (*ConfigApplyFailure)(nil), // 199: openshell.v1.ConfigApplyFailure + (*ConfigUpdateResult)(nil), // 200: openshell.v1.ConfigUpdateResult + (*PolicyLoadResult)(nil), // 201: openshell.v1.PolicyLoadResult + (*SessionRejected)(nil), // 202: openshell.v1.SessionRejected + (*SessionRedirect)(nil), // 203: openshell.v1.SessionRedirect + (*SupervisorHeartbeat)(nil), // 204: openshell.v1.SupervisorHeartbeat + (*GatewayHeartbeat)(nil), // 205: openshell.v1.GatewayHeartbeat + (*ReportMainProcessExitRequest)(nil), // 206: openshell.v1.ReportMainProcessExitRequest + (*ReportMainProcessExitResponse)(nil), // 207: openshell.v1.ReportMainProcessExitResponse + (*FinalizeMainProcessExitRequest)(nil), // 208: openshell.v1.FinalizeMainProcessExitRequest + (*FinalizeMainProcessExitResponse)(nil), // 209: openshell.v1.FinalizeMainProcessExitResponse + (*RelayOpen)(nil), // 210: openshell.v1.RelayOpen + (*SshRelayTarget)(nil), // 211: openshell.v1.SshRelayTarget + (*TcpRelayTarget)(nil), // 212: openshell.v1.TcpRelayTarget + (*RelayInit)(nil), // 213: openshell.v1.RelayInit + (*RelayFrame)(nil), // 214: openshell.v1.RelayFrame + (*PeerRelayInit)(nil), // 215: openshell.v1.PeerRelayInit + (*PeerRelayFrame)(nil), // 216: openshell.v1.PeerRelayFrame + (*PeerConfigSandboxTarget)(nil), // 217: openshell.v1.PeerConfigSandboxTarget + (*PeerConfigProviderTarget)(nil), // 218: openshell.v1.PeerConfigProviderTarget + (*PeerNotifyConfigUpdateRequest)(nil), // 219: openshell.v1.PeerNotifyConfigUpdateRequest + (*PeerNotifyConfigUpdateResponse)(nil), // 220: openshell.v1.PeerNotifyConfigUpdateResponse + (*RelayOpenResult)(nil), // 221: openshell.v1.RelayOpenResult + (*RelayClose)(nil), // 222: openshell.v1.RelayClose + (*L7RequestSample)(nil), // 223: openshell.v1.L7RequestSample + (*DenialSummary)(nil), // 224: openshell.v1.DenialSummary + (*DenialGroupCount)(nil), // 225: openshell.v1.DenialGroupCount + (*NetworkActivitySummary)(nil), // 226: openshell.v1.NetworkActivitySummary + (*PolicyChunk)(nil), // 227: openshell.v1.PolicyChunk + (*DraftPolicyUpdate)(nil), // 228: openshell.v1.DraftPolicyUpdate + (*SubmitPolicyAnalysisRequest)(nil), // 229: openshell.v1.SubmitPolicyAnalysisRequest + (*SubmitPolicyAnalysisResponse)(nil), // 230: openshell.v1.SubmitPolicyAnalysisResponse + (*GetDraftPolicyRequest)(nil), // 231: openshell.v1.GetDraftPolicyRequest + (*GetDraftPolicyResponse)(nil), // 232: openshell.v1.GetDraftPolicyResponse + (*ApproveDraftChunkRequest)(nil), // 233: openshell.v1.ApproveDraftChunkRequest + (*ApproveDraftChunkResponse)(nil), // 234: openshell.v1.ApproveDraftChunkResponse + (*RejectDraftChunkRequest)(nil), // 235: openshell.v1.RejectDraftChunkRequest + (*RejectDraftChunkResponse)(nil), // 236: openshell.v1.RejectDraftChunkResponse + (*DraftChunkApproval)(nil), // 237: openshell.v1.DraftChunkApproval + (*ApproveAllDraftChunksRequest)(nil), // 238: openshell.v1.ApproveAllDraftChunksRequest + (*ApproveAllDraftChunksResponse)(nil), // 239: openshell.v1.ApproveAllDraftChunksResponse + (*EditDraftChunkRequest)(nil), // 240: openshell.v1.EditDraftChunkRequest + (*EditDraftChunkResponse)(nil), // 241: openshell.v1.EditDraftChunkResponse + (*UndoDraftChunkRequest)(nil), // 242: openshell.v1.UndoDraftChunkRequest + (*UndoDraftChunkResponse)(nil), // 243: openshell.v1.UndoDraftChunkResponse + (*ClearDraftChunksRequest)(nil), // 244: openshell.v1.ClearDraftChunksRequest + (*ClearDraftChunksResponse)(nil), // 245: openshell.v1.ClearDraftChunksResponse + (*GetDraftHistoryRequest)(nil), // 246: openshell.v1.GetDraftHistoryRequest + (*DraftHistoryEntry)(nil), // 247: openshell.v1.DraftHistoryEntry + (*GetDraftHistoryResponse)(nil), // 248: openshell.v1.GetDraftHistoryResponse + (*CreateWorkspaceRequest)(nil), // 249: openshell.v1.CreateWorkspaceRequest + (*CreateWorkspaceResponse)(nil), // 250: openshell.v1.CreateWorkspaceResponse + (*GetWorkspaceRequest)(nil), // 251: openshell.v1.GetWorkspaceRequest + (*GetWorkspaceResponse)(nil), // 252: openshell.v1.GetWorkspaceResponse + (*ListWorkspacesRequest)(nil), // 253: openshell.v1.ListWorkspacesRequest + (*ListWorkspacesResponse)(nil), // 254: openshell.v1.ListWorkspacesResponse + (*DeleteWorkspaceRequest)(nil), // 255: openshell.v1.DeleteWorkspaceRequest + (*DeleteWorkspaceResponse)(nil), // 256: openshell.v1.DeleteWorkspaceResponse + (*WorkspaceMember)(nil), // 257: openshell.v1.WorkspaceMember + (*AddWorkspaceMemberRequest)(nil), // 258: openshell.v1.AddWorkspaceMemberRequest + (*AddWorkspaceMemberResponse)(nil), // 259: openshell.v1.AddWorkspaceMemberResponse + (*RemoveWorkspaceMemberRequest)(nil), // 260: openshell.v1.RemoveWorkspaceMemberRequest + (*RemoveWorkspaceMemberResponse)(nil), // 261: openshell.v1.RemoveWorkspaceMemberResponse + (*ListWorkspaceMembersRequest)(nil), // 262: openshell.v1.ListWorkspaceMembersRequest + (*ListWorkspaceMembersResponse)(nil), // 263: openshell.v1.ListWorkspaceMembersResponse + (*ExtensionServiceCredential)(nil), // 264: openshell.v1.ExtensionServiceCredential + (*EndpointObservation)(nil), // 265: openshell.v1.EndpointObservation + (*ReportEndpointStatusRequest)(nil), // 266: openshell.v1.ReportEndpointStatusRequest + (*TokenGrantStatus)(nil), // 267: openshell.v1.TokenGrantStatus + (*TokenGrantFailureObservation)(nil), // 268: openshell.v1.TokenGrantFailureObservation + (*ReportEndpointStatusResponse)(nil), // 269: openshell.v1.ReportEndpointStatusResponse + (*EndpointStatus)(nil), // 270: openshell.v1.EndpointStatus + (*SandboxProvisioning)(nil), // 271: openshell.v1.SandboxProvisioning + (*SandboxServiceExposure)(nil), // 272: openshell.v1.SandboxServiceExposure + (*EndpointFailure)(nil), // 273: openshell.v1.EndpointFailure + nil, // 274: openshell.v1.SandboxSpec.EnvironmentEntry + nil, // 275: openshell.v1.SandboxTemplate.LabelsEntry + nil, // 276: openshell.v1.SandboxTemplate.AnnotationsEntry + nil, // 277: openshell.v1.SandboxTemplate.EnvironmentEntry + nil, // 278: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + nil, // 279: openshell.v1.PlatformEvent.MetadataEntry + nil, // 280: openshell.v1.CreateSandboxRequest.LabelsEntry + nil, // 281: openshell.v1.CreateSandboxRequest.AnnotationsEntry + nil, // 282: openshell.v1.SandboxMapUpdate.ValuesEntry + 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.GetSandboxConfigRequest)(nil), // 317: openshell.sandbox.v1.GetSandboxConfigRequest + (*sandboxv1.GetGatewayConfigRequest)(nil), // 318: openshell.sandbox.v1.GetGatewayConfigRequest + (*sandboxv1.GetSandboxConfigResponse)(nil), // 319: openshell.sandbox.v1.GetSandboxConfigResponse + (*sandboxv1.GetGatewayConfigResponse)(nil), // 320: openshell.sandbox.v1.GetGatewayConfigResponse } var file_openshell_proto_depIdxs = []int32{ - 297, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp - 297, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp - 261, // 2: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential - 297, // 3: openshell.v1.RefreshSandboxTokenResponse.sandbox_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 + 264, // 2: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential + 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 @@ -21536,524 +21771,532 @@ 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 - 298, // 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 - 271, // 18: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry + 274, // 18: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry 43, // 19: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate - 299, // 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 - 272, // 24: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry - 273, // 25: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry - 274, // 26: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry - 300, // 27: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct - 300, // 28: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct - 298, // 29: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 275, // 24: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry + 276, // 25: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry + 277, // 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 - 300, // 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 - 275, // 34: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + 278, // 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 - 301, // 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 - 267, // 41: openshell.v1.SandboxStatus.endpoint_statuses:type_name -> openshell.v1.EndpointStatus - 183, // 42: openshell.v1.SandboxStatus.configuration_admission:type_name -> openshell.v1.SandboxConfigurationAdmission - 268, // 43: openshell.v1.SandboxStatus.provisioning:type_name -> openshell.v1.SandboxProvisioning - 297, // 44: openshell.v1.SandboxStatus.next_restart_time:type_name -> google.protobuf.Timestamp - 297, // 45: openshell.v1.SandboxStatus.main_process_started_time:type_name -> google.protobuf.Timestamp - 265, // 46: openshell.v1.SandboxStatus.last_token_grant_failure:type_name -> openshell.v1.TokenGrantFailureObservation - 297, // 47: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp - 297, // 48: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp - 276, // 49: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry - 302, // 50: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 270, // 41: openshell.v1.SandboxStatus.endpoint_statuses:type_name -> openshell.v1.EndpointStatus + 186, // 42: openshell.v1.SandboxStatus.configuration_admission:type_name -> openshell.v1.SandboxConfigurationAdmission + 271, // 43: openshell.v1.SandboxStatus.provisioning:type_name -> openshell.v1.SandboxProvisioning + 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 + 268, // 46: openshell.v1.SandboxStatus.last_token_grant_failure:type_name -> openshell.v1.TokenGrantFailureObservation + 301, // 47: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp + 301, // 48: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp + 279, // 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 - 277, // 52: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry - 278, // 53: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry - 269, // 54: openshell.v1.CreateSandboxRequest.service_exposures:type_name -> openshell.v1.SandboxServiceExposure - 302, // 55: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 44, // 56: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate - 302, // 57: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 58: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 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 - 302, // 63: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 297, // 64: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp - 302, // 65: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 66: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 67: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 68: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 69: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 70: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 71: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 72: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 39, // 73: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox - 279, // 74: openshell.v1.SandboxResponse.service_urls:type_name -> openshell.v1.SandboxResponse.ServiceUrlsEntry - 39, // 75: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox - 303, // 76: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 39, // 77: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 81, // 78: openshell.v1.AttachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt - 39, // 79: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 81, // 80: openshell.v1.DetachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt - 79, // 81: openshell.v1.ConfigSnapshotRevision.sandbox_config:type_name -> openshell.v1.SandboxConfigRevision - 77, // 82: openshell.v1.ConfigSnapshotRevision.provider_target:type_name -> openshell.v1.ProviderDesiredIdentity - 304, // 83: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource - 5, // 84: openshell.v1.ConfigUpdateOperation.component:type_name -> openshell.v1.ConfigComponent - 78, // 85: openshell.v1.ConfigUpdateOperation.target_revision:type_name -> openshell.v1.ConfigSnapshotRevision - 7, // 86: openshell.v1.ConfigUpdateOperation.state:type_name -> openshell.v1.ConfigUpdateOperationState - 6, // 87: openshell.v1.ConfigUpdateOperation.outcome:type_name -> openshell.v1.ConfigApplyOutcome - 297, // 88: openshell.v1.ConfigUpdateOperation.created_time:type_name -> google.protobuf.Timestamp - 297, // 89: openshell.v1.ConfigUpdateOperation.updated_time:type_name -> google.protobuf.Timestamp - 297, // 90: openshell.v1.ConfigUpdateOperation.completed_time:type_name -> google.protobuf.Timestamp - 2, // 91: openshell.v1.ProviderMutationReceipt.kind:type_name -> openshell.v1.ProviderMutationKind - 77, // 92: openshell.v1.ProviderMutationReceipt.desired:type_name -> openshell.v1.ProviderDesiredIdentity - 297, // 93: openshell.v1.ProviderMutationReceipt.persisted_time:type_name -> google.protobuf.Timestamp - 4, // 94: openshell.v1.ProviderReadinessObservation.reason:type_name -> openshell.v1.ProviderReadinessReason - 81, // 95: openshell.v1.ProviderReadinessStatus.receipt:type_name -> openshell.v1.ProviderMutationReceipt - 3, // 96: openshell.v1.ProviderReadinessStatus.state:type_name -> openshell.v1.ProviderReadinessState - 4, // 97: openshell.v1.ProviderReadinessStatus.reason:type_name -> openshell.v1.ProviderReadinessReason - 82, // 98: openshell.v1.ProviderReadinessStatus.observed:type_name -> openshell.v1.ProviderReadinessObservation - 297, // 99: openshell.v1.ProviderReadinessStatus.observed_time:type_name -> google.protobuf.Timestamp - 297, // 100: openshell.v1.ProviderReadinessStatus.evaluated_time:type_name -> google.protobuf.Timestamp - 80, // 101: openshell.v1.ProviderReadinessStatus.operation:type_name -> openshell.v1.ConfigUpdateOperation - 302, // 102: openshell.v1.GetSandboxProviderStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 83, // 103: openshell.v1.GetSandboxProviderStatusResponse.status:type_name -> openshell.v1.ProviderReadinessStatus - 82, // 104: openshell.v1.ReportProviderReadinessRequest.observation:type_name -> openshell.v1.ProviderReadinessObservation - 301, // 105: openshell.v1.ReportProviderReadinessResponse.report_interval:type_name -> google.protobuf.Duration - 301, // 106: openshell.v1.ReportProviderReadinessResponse.observation_ttl:type_name -> google.protobuf.Duration - 18, // 107: openshell.v1.DeleteSandboxResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 302, // 108: openshell.v1.CreateSshSessionRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 297, // 109: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp - 302, // 110: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 20, // 111: openshell.v1.ExposeServiceRequest.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode - 302, // 112: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 113: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 98, // 114: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse - 302, // 115: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 18, // 116: openshell.v1.DeleteServiceResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 298, // 117: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 20, // 118: openshell.v1.ServiceEndpoint.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode - 97, // 119: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint - 18, // 120: openshell.v1.RevokeSshSessionResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 302, // 121: openshell.v1.ExecSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 280, // 122: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry - 301, // 123: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration - 102, // 124: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout - 103, // 125: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr - 104, // 126: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit - 208, // 127: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget - 209, // 128: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget - 106, // 129: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit - 101, // 130: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest - 109, // 131: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize - 298, // 132: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 297, // 133: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp - 302, // 134: openshell.v1.WatchSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 297, // 135: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp - 39, // 136: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox - 113, // 137: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine - 53, // 138: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent - 114, // 139: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning - 225, // 140: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate - 297, // 141: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp - 281, // 142: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry - 302, // 143: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 303, // 144: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 302, // 145: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 146: openshell.v1.GetProviderCredentialsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 301, // 147: openshell.v1.GetProviderCredentialsRequest.minimum_remaining_lifetime:type_name -> google.protobuf.Duration - 297, // 148: openshell.v1.ProviderCredentialValue.expiration_time:type_name -> google.protobuf.Timestamp - 282, // 149: openshell.v1.GetProviderCredentialsResponse.credentials:type_name -> openshell.v1.GetProviderCredentialsResponse.CredentialsEntry - 302, // 150: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 151: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 303, // 152: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 283, // 153: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - 302, // 154: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 303, // 155: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider - 81, // 156: openshell.v1.ProviderResponse.target_receipts:type_name -> openshell.v1.ProviderMutationReceipt - 303, // 157: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 302, // 158: openshell.v1.ListProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 159: openshell.v1.GetProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 146, // 160: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile - 301, // 161: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration - 129, // 162: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride - 8, // 163: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType - 130, // 164: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken - 135, // 165: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh - 131, // 166: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant - 9, // 167: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 301, // 168: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration - 301, // 169: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration - 133, // 170: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial - 134, // 171: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput - 9, // 172: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 297, // 173: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp - 297, // 174: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp - 297, // 175: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp - 16, // 176: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 297, // 177: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp - 302, // 178: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 136, // 179: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 302, // 180: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 9, // 181: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 284, // 182: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - 297, // 183: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp - 136, // 184: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 302, // 185: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 136, // 186: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 302, // 187: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 18, // 188: openshell.v1.DeleteProviderRefreshResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 10, // 189: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory - 132, // 190: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential - 305, // 191: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint - 306, // 192: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 137, // 193: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery - 285, // 194: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry - 147, // 195: openshell.v1.ProviderProfile.files:type_name -> openshell.v1.ProviderProfileFile - 146, // 196: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile - 146, // 197: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 302, // 198: openshell.v1.ImportProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 127, // 199: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 128, // 200: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 146, // 201: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 302, // 202: openshell.v1.UpdateProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 127, // 203: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem - 128, // 204: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 146, // 205: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile - 302, // 206: openshell.v1.LintProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 127, // 207: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 128, // 208: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 18, // 209: openshell.v1.DeleteProviderResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 302, // 210: openshell.v1.DeleteProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 18, // 211: openshell.v1.DeleteProviderProfileResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 160, // 212: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding - 286, // 213: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - 287, // 214: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - 288, // 215: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - 289, // 216: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - 4, // 217: openshell.v1.GetSandboxProviderEnvironmentResponse.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason - 290, // 218: openshell.v1.GetSandboxProviderEnvironmentResponse.files:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry - 11, // 219: openshell.v1.ProviderEnvironmentValue.classification:type_name -> openshell.v1.ProviderEnvironmentValueClassification - 161, // 220: openshell.v1.ProviderEnvironmentValue.static_credential_binding:type_name -> openshell.v1.StaticCredentialBinding - 297, // 221: openshell.v1.ProviderEnvironmentValue.expiration_time:type_name -> google.protobuf.Timestamp - 163, // 222: openshell.v1.ProviderEnvironmentSnapshot.values:type_name -> openshell.v1.ProviderEnvironmentValue - 291, // 223: openshell.v1.ProviderEnvironmentSnapshot.dynamic_credentials:type_name -> openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry - 4, // 224: openshell.v1.ProviderEnvironmentSnapshot.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason - 292, // 225: openshell.v1.ProviderEnvironmentSnapshot.files:type_name -> openshell.v1.ProviderEnvironmentSnapshot.FilesEntry - 301, // 226: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration - 302, // 227: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 299, // 228: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 307, // 229: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue - 168, // 230: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation - 293, // 231: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry - 169, // 232: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule - 170, // 233: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint - 171, // 234: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule - 173, // 235: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules - 174, // 236: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules - 175, // 237: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary - 308, // 238: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 306, // 239: openshell.v1.L7RuleTarget.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 309, // 240: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule - 172, // 241: openshell.v1.AddDenyRules.target:type_name -> openshell.v1.L7RuleTarget - 310, // 242: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule - 172, // 243: openshell.v1.AddAllowRules.target:type_name -> openshell.v1.L7RuleTarget - 294, // 244: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry - 302, // 245: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 186, // 246: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision - 302, // 247: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 186, // 248: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision - 13, // 249: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus - 12, // 250: openshell.v1.SandboxConfigurationAdmission.state:type_name -> openshell.v1.ConfigurationAdmissionState - 183, // 251: openshell.v1.ReportSandboxConfigurationRequest.admission:type_name -> openshell.v1.SandboxConfigurationAdmission - 13, // 252: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus - 297, // 253: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp - 297, // 254: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp - 299, // 255: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 295, // 256: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry - 302, // 257: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 297, // 258: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp - 113, // 259: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine - 113, // 260: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine - 193, // 261: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello - 201, // 262: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat - 218, // 263: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult - 219, // 264: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose - 197, // 265: openshell.v1.SupervisorMessage.config_update_result:type_name -> openshell.v1.ConfigUpdateResult - 194, // 266: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted - 199, // 267: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected - 202, // 268: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat - 207, // 269: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen - 219, // 270: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose - 200, // 271: openshell.v1.GatewayMessage.session_redirect:type_name -> openshell.v1.SessionRedirect - 195, // 272: openshell.v1.GatewayMessage.config_update:type_name -> openshell.v1.ConfigUpdate - 195, // 273: openshell.v1.SessionAccepted.bootstrap:type_name -> openshell.v1.ConfigUpdate - 301, // 274: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration - 311, // 275: openshell.v1.ConfigUpdate.sandbox_config:type_name -> openshell.sandbox.v1.SandboxConfigSnapshot - 164, // 276: openshell.v1.ConfigUpdate.provider_environment:type_name -> openshell.v1.ProviderEnvironmentSnapshot - 6, // 277: openshell.v1.ConfigUpdateResult.outcome:type_name -> openshell.v1.ConfigApplyOutcome - 196, // 278: openshell.v1.ConfigUpdateResult.failure:type_name -> openshell.v1.ConfigApplyFailure - 183, // 279: openshell.v1.ConfigUpdateResult.admission:type_name -> openshell.v1.SandboxConfigurationAdmission - 198, // 280: openshell.v1.ConfigUpdateResult.policy_load:type_name -> openshell.v1.PolicyLoadResult - 208, // 281: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget - 209, // 282: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget - 210, // 283: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit - 207, // 284: openshell.v1.PeerRelayInit.relay_open:type_name -> openshell.v1.RelayOpen - 212, // 285: openshell.v1.PeerRelayFrame.init:type_name -> openshell.v1.PeerRelayInit - 214, // 286: openshell.v1.PeerNotifyConfigUpdateRequest.sandbox:type_name -> openshell.v1.PeerConfigSandboxTarget - 215, // 287: openshell.v1.PeerNotifyConfigUpdateRequest.provider:type_name -> openshell.v1.PeerConfigProviderTarget - 297, // 288: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp - 297, // 289: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp - 220, // 290: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample - 222, // 291: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount - 308, // 292: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 297, // 293: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp - 297, // 294: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp - 297, // 295: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp - 297, // 296: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp - 299, // 297: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 299, // 298: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 302, // 299: openshell.v1.SubmitPolicyAnalysisRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 221, // 300: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary - 224, // 301: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk - 223, // 302: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary - 302, // 303: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 224, // 304: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk - 297, // 305: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp - 302, // 306: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 307: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 308: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 234, // 309: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval - 302, // 310: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 311: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 302, // 312: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 313: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 302, // 314: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 297, // 315: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp - 244, // 316: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry - 296, // 317: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry - 312, // 318: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 312, // 319: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 312, // 320: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace - 18, // 321: openshell.v1.DeleteWorkspaceResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 298, // 322: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 15, // 323: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole - 302, // 324: openshell.v1.AddWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 15, // 325: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole - 254, // 326: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember - 302, // 327: openshell.v1.RemoveWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 18, // 328: openshell.v1.RemoveWorkspaceMemberResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 302, // 329: openshell.v1.ListWorkspaceMembersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 330: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember - 297, // 331: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp - 19, // 332: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult - 270, // 333: openshell.v1.EndpointObservation.failure:type_name -> openshell.v1.EndpointFailure - 262, // 334: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation - 264, // 335: openshell.v1.ReportEndpointStatusRequest.token_grant_status:type_name -> openshell.v1.TokenGrantStatus - 265, // 336: openshell.v1.TokenGrantStatus.last_failure:type_name -> openshell.v1.TokenGrantFailureObservation - 270, // 337: openshell.v1.TokenGrantFailureObservation.failure:type_name -> openshell.v1.EndpointFailure - 19, // 338: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult - 297, // 339: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp - 270, // 340: openshell.v1.EndpointStatus.last_failure:type_name -> openshell.v1.EndpointFailure - 297, // 341: openshell.v1.SandboxProvisioning.configuration_change_time:type_name -> google.protobuf.Timestamp - 297, // 342: openshell.v1.SandboxProvisioning.first_rejection_time:type_name -> google.protobuf.Timestamp - 297, // 343: openshell.v1.SandboxProvisioning.deadline:type_name -> google.protobuf.Timestamp - 297, // 344: openshell.v1.SandboxProvisioning.timeout_time:type_name -> google.protobuf.Timestamp - 297, // 345: openshell.v1.SandboxProvisioning.cleanup_completed_time:type_name -> google.protobuf.Timestamp - 297, // 346: openshell.v1.SandboxProvisioning.cleanup_retry_time:type_name -> google.protobuf.Timestamp - 297, // 347: openshell.v1.SandboxProvisioning.attachment_change_time:type_name -> google.protobuf.Timestamp - 297, // 348: openshell.v1.SandboxProvisioning.preparation_deadline:type_name -> google.protobuf.Timestamp - 297, // 349: openshell.v1.SandboxProvisioning.admission_start_time:type_name -> google.protobuf.Timestamp - 20, // 350: openshell.v1.SandboxServiceExposure.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode - 21, // 351: openshell.v1.EndpointFailure.domain:type_name -> openshell.v1.EndpointFailureDomain - 16, // 352: openshell.v1.EndpointFailure.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 118, // 353: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry.value:type_name -> openshell.v1.ProviderCredentialValue - 297, // 354: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 297, // 355: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 132, // 356: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 161, // 357: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding - 132, // 358: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 26, // 359: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest - 28, // 360: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest - 30, // 361: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest - 54, // 362: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest - 62, // 363: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest - 64, // 364: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest - 65, // 365: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest - 55, // 366: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest - 56, // 367: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest - 57, // 368: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest - 58, // 369: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest - 66, // 370: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest - 67, // 371: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest - 68, // 372: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest - 84, // 373: openshell.v1.OpenShell.GetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest - 69, // 374: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest - 70, // 375: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest - 71, // 376: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest - 89, // 377: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest - 91, // 378: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest - 92, // 379: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest - 93, // 380: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest - 95, // 381: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest - 99, // 382: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest - 101, // 383: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest - 107, // 384: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame - 108, // 385: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput - 115, // 386: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest - 116, // 387: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest - 117, // 388: openshell.v1.OpenShell.GetProviderCredentials:input_type -> openshell.v1.GetProviderCredentialsRequest - 120, // 389: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest - 125, // 390: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest - 126, // 391: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest - 150, // 392: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest - 152, // 393: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest - 154, // 394: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest - 121, // 395: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest - 138, // 396: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest - 140, // 397: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest - 142, // 398: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest - 144, // 399: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest - 122, // 400: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest - 157, // 401: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest - 313, // 402: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest - 314, // 403: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest - 167, // 404: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest - 177, // 405: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest - 179, // 406: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest - 181, // 407: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest - 263, // 408: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest - 86, // 409: openshell.v1.OpenShell.ReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest - 184, // 410: openshell.v1.OpenShell.ReportSandboxConfiguration:input_type -> openshell.v1.ReportSandboxConfigurationRequest - 159, // 411: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest - 165, // 412: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest - 187, // 413: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest - 188, // 414: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest - 191, // 415: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage - 203, // 416: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest - 205, // 417: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest - 211, // 418: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame - 213, // 419: openshell.v1.OpenShell.PeerRelay:input_type -> openshell.v1.PeerRelayFrame - 86, // 420: openshell.v1.OpenShell.PeerReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest - 263, // 421: openshell.v1.OpenShell.PeerReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest - 84, // 422: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest - 216, // 423: openshell.v1.OpenShell.PeerNotifyConfigUpdate:input_type -> openshell.v1.PeerNotifyConfigUpdateRequest - 111, // 424: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest - 226, // 425: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest - 228, // 426: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest - 230, // 427: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest - 232, // 428: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest - 235, // 429: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest - 237, // 430: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest - 239, // 431: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest - 241, // 432: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest - 243, // 433: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest - 22, // 434: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest - 24, // 435: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest - 246, // 436: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest - 248, // 437: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest - 250, // 438: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest - 252, // 439: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest - 255, // 440: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest - 257, // 441: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest - 259, // 442: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest - 27, // 443: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse - 29, // 444: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse - 31, // 445: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse - 72, // 446: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse - 63, // 447: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse - 72, // 448: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse - 73, // 449: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse - 59, // 450: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 59, // 451: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 60, // 452: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse - 61, // 453: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse - 74, // 454: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse - 75, // 455: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse - 76, // 456: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse - 85, // 457: openshell.v1.OpenShell.GetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse - 88, // 458: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse - 72, // 459: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse - 72, // 460: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse - 90, // 461: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse - 98, // 462: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse - 98, // 463: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse - 94, // 464: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse - 96, // 465: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse - 100, // 466: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse - 105, // 467: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent - 107, // 468: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame - 105, // 469: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent - 123, // 470: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse - 123, // 471: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse - 119, // 472: openshell.v1.OpenShell.GetProviderCredentials:output_type -> openshell.v1.GetProviderCredentialsResponse - 124, // 473: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse - 149, // 474: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse - 148, // 475: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse - 151, // 476: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse - 153, // 477: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse - 155, // 478: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse - 123, // 479: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse - 139, // 480: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse - 141, // 481: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse - 143, // 482: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse - 145, // 483: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse - 156, // 484: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse - 158, // 485: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse - 315, // 486: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse - 316, // 487: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse - 176, // 488: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse - 178, // 489: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse - 180, // 490: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse - 182, // 491: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse - 266, // 492: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse - 87, // 493: openshell.v1.OpenShell.ReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse - 185, // 494: openshell.v1.OpenShell.ReportSandboxConfiguration:output_type -> openshell.v1.ReportSandboxConfigurationResponse - 162, // 495: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse - 166, // 496: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse - 190, // 497: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse - 189, // 498: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse - 192, // 499: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage - 204, // 500: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse - 206, // 501: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse - 211, // 502: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame - 213, // 503: openshell.v1.OpenShell.PeerRelay:output_type -> openshell.v1.PeerRelayFrame - 87, // 504: openshell.v1.OpenShell.PeerReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse - 266, // 505: openshell.v1.OpenShell.PeerReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse - 85, // 506: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse - 217, // 507: openshell.v1.OpenShell.PeerNotifyConfigUpdate:output_type -> openshell.v1.PeerNotifyConfigUpdateResponse - 112, // 508: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent - 227, // 509: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse - 229, // 510: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse - 231, // 511: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse - 233, // 512: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse - 236, // 513: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse - 238, // 514: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse - 240, // 515: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse - 242, // 516: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse - 245, // 517: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse - 23, // 518: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse - 25, // 519: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse - 247, // 520: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse - 249, // 521: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse - 251, // 522: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse - 253, // 523: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse - 256, // 524: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse - 258, // 525: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse - 260, // 526: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse - 443, // [443:527] is the sub-list for method output_type - 359, // [359:443] is the sub-list for method input_type - 359, // [359:359] is the sub-list for extension type_name - 359, // [359:359] is the sub-list for extension extendee - 0, // [0:359] is the sub-list for field type_name + 280, // 52: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry + 281, // 53: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry + 272, // 54: openshell.v1.CreateSandboxRequest.service_exposures:type_name -> openshell.v1.SandboxServiceExposure + 306, // 55: openshell.v1.UpdateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 56, // 56: openshell.v1.UpdateSandboxRequest.command:type_name -> openshell.v1.SandboxCommandUpdate + 57, // 57: openshell.v1.UpdateSandboxRequest.environment:type_name -> openshell.v1.SandboxMapUpdate + 57, // 58: openshell.v1.UpdateSandboxRequest.labels:type_name -> openshell.v1.SandboxMapUpdate + 57, // 59: openshell.v1.UpdateSandboxRequest.annotations:type_name -> openshell.v1.SandboxMapUpdate + 282, // 60: openshell.v1.SandboxMapUpdate.values:type_name -> openshell.v1.SandboxMapUpdate.ValuesEntry + 306, // 61: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 44, // 62: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate + 306, // 63: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 64: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 65: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 44, // 66: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate + 44, // 67: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate + 18, // 68: openshell.v1.DeleteSandboxTemplateResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 306, // 69: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 301, // 70: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp + 306, // 71: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 72: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 73: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 74: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 75: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 76: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 77: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 78: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 39, // 79: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox + 283, // 80: openshell.v1.SandboxResponse.service_urls:type_name -> openshell.v1.SandboxResponse.ServiceUrlsEntry + 39, // 81: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox + 307, // 82: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 39, // 83: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 84, // 84: openshell.v1.AttachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt + 39, // 85: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 84, // 86: openshell.v1.DetachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt + 82, // 87: openshell.v1.ConfigSnapshotRevision.sandbox_config:type_name -> openshell.v1.SandboxConfigRevision + 80, // 88: openshell.v1.ConfigSnapshotRevision.provider_target:type_name -> openshell.v1.ProviderDesiredIdentity + 308, // 89: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource + 5, // 90: openshell.v1.ConfigUpdateOperation.component:type_name -> openshell.v1.ConfigComponent + 81, // 91: openshell.v1.ConfigUpdateOperation.target_revision:type_name -> openshell.v1.ConfigSnapshotRevision + 7, // 92: openshell.v1.ConfigUpdateOperation.state:type_name -> openshell.v1.ConfigUpdateOperationState + 6, // 93: openshell.v1.ConfigUpdateOperation.outcome:type_name -> openshell.v1.ConfigApplyOutcome + 301, // 94: openshell.v1.ConfigUpdateOperation.created_time:type_name -> google.protobuf.Timestamp + 301, // 95: openshell.v1.ConfigUpdateOperation.updated_time:type_name -> google.protobuf.Timestamp + 301, // 96: openshell.v1.ConfigUpdateOperation.completed_time:type_name -> google.protobuf.Timestamp + 2, // 97: openshell.v1.ProviderMutationReceipt.kind:type_name -> openshell.v1.ProviderMutationKind + 80, // 98: openshell.v1.ProviderMutationReceipt.desired:type_name -> openshell.v1.ProviderDesiredIdentity + 301, // 99: openshell.v1.ProviderMutationReceipt.persisted_time:type_name -> google.protobuf.Timestamp + 4, // 100: openshell.v1.ProviderReadinessObservation.reason:type_name -> openshell.v1.ProviderReadinessReason + 84, // 101: openshell.v1.ProviderReadinessStatus.receipt:type_name -> openshell.v1.ProviderMutationReceipt + 3, // 102: openshell.v1.ProviderReadinessStatus.state:type_name -> openshell.v1.ProviderReadinessState + 4, // 103: openshell.v1.ProviderReadinessStatus.reason:type_name -> openshell.v1.ProviderReadinessReason + 85, // 104: openshell.v1.ProviderReadinessStatus.observed:type_name -> openshell.v1.ProviderReadinessObservation + 301, // 105: openshell.v1.ProviderReadinessStatus.observed_time:type_name -> google.protobuf.Timestamp + 301, // 106: openshell.v1.ProviderReadinessStatus.evaluated_time:type_name -> google.protobuf.Timestamp + 83, // 107: openshell.v1.ProviderReadinessStatus.operation:type_name -> openshell.v1.ConfigUpdateOperation + 306, // 108: openshell.v1.GetSandboxProviderStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 86, // 109: openshell.v1.GetSandboxProviderStatusResponse.status:type_name -> openshell.v1.ProviderReadinessStatus + 85, // 110: openshell.v1.ReportProviderReadinessRequest.observation:type_name -> openshell.v1.ProviderReadinessObservation + 305, // 111: openshell.v1.ReportProviderReadinessResponse.report_interval:type_name -> google.protobuf.Duration + 305, // 112: openshell.v1.ReportProviderReadinessResponse.observation_ttl:type_name -> google.protobuf.Duration + 18, // 113: openshell.v1.DeleteSandboxResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 306, // 114: openshell.v1.CreateSshSessionRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 301, // 115: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp + 306, // 116: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 20, // 117: openshell.v1.ExposeServiceRequest.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode + 306, // 118: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 119: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 101, // 120: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse + 306, // 121: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 18, // 122: openshell.v1.DeleteServiceResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 302, // 123: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 20, // 124: openshell.v1.ServiceEndpoint.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode + 100, // 125: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint + 18, // 126: openshell.v1.RevokeSshSessionResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 306, // 127: openshell.v1.ExecSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 284, // 128: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry + 305, // 129: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration + 105, // 130: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout + 106, // 131: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr + 107, // 132: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit + 211, // 133: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget + 212, // 134: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget + 109, // 135: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit + 104, // 136: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest + 112, // 137: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize + 302, // 138: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 301, // 139: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp + 306, // 140: openshell.v1.WatchSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 301, // 141: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp + 39, // 142: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox + 116, // 143: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine + 53, // 144: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent + 117, // 145: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning + 228, // 146: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate + 301, // 147: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp + 285, // 148: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry + 306, // 149: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 307, // 150: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 306, // 151: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 152: openshell.v1.GetProviderCredentialsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 305, // 153: openshell.v1.GetProviderCredentialsRequest.minimum_remaining_lifetime:type_name -> google.protobuf.Duration + 301, // 154: openshell.v1.ProviderCredentialValue.expiration_time:type_name -> google.protobuf.Timestamp + 286, // 155: openshell.v1.GetProviderCredentialsResponse.credentials:type_name -> openshell.v1.GetProviderCredentialsResponse.CredentialsEntry + 306, // 156: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 157: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 307, // 158: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 287, // 159: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + 306, // 160: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 307, // 161: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider + 84, // 162: openshell.v1.ProviderResponse.target_receipts:type_name -> openshell.v1.ProviderMutationReceipt + 307, // 163: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 306, // 164: openshell.v1.ListProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 165: openshell.v1.GetProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 149, // 166: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile + 305, // 167: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration + 132, // 168: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride + 8, // 169: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType + 133, // 170: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken + 138, // 171: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh + 134, // 172: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant + 9, // 173: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 305, // 174: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration + 305, // 175: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration + 136, // 176: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial + 137, // 177: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput + 9, // 178: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 301, // 179: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp + 301, // 180: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp + 301, // 181: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp + 16, // 182: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction + 301, // 183: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp + 306, // 184: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 139, // 185: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 306, // 186: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 9, // 187: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 288, // 188: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + 301, // 189: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp + 139, // 190: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 306, // 191: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 139, // 192: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 306, // 193: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 18, // 194: openshell.v1.DeleteProviderRefreshResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 10, // 195: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory + 135, // 196: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential + 309, // 197: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 310, // 198: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 140, // 199: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery + 289, // 200: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry + 150, // 201: openshell.v1.ProviderProfile.files:type_name -> openshell.v1.ProviderProfileFile + 149, // 202: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile + 149, // 203: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 306, // 204: openshell.v1.ImportProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 130, // 205: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 131, // 206: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 149, // 207: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 306, // 208: openshell.v1.UpdateProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 130, // 209: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem + 131, // 210: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 149, // 211: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile + 306, // 212: openshell.v1.LintProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 130, // 213: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 131, // 214: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 18, // 215: openshell.v1.DeleteProviderResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 306, // 216: openshell.v1.DeleteProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 18, // 217: openshell.v1.DeleteProviderProfileResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 163, // 218: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding + 290, // 219: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + 291, // 220: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + 292, // 221: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + 293, // 222: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + 4, // 223: openshell.v1.GetSandboxProviderEnvironmentResponse.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason + 294, // 224: openshell.v1.GetSandboxProviderEnvironmentResponse.files:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry + 11, // 225: openshell.v1.ProviderEnvironmentValue.classification:type_name -> openshell.v1.ProviderEnvironmentValueClassification + 164, // 226: openshell.v1.ProviderEnvironmentValue.static_credential_binding:type_name -> openshell.v1.StaticCredentialBinding + 301, // 227: openshell.v1.ProviderEnvironmentValue.expiration_time:type_name -> google.protobuf.Timestamp + 166, // 228: openshell.v1.ProviderEnvironmentSnapshot.values:type_name -> openshell.v1.ProviderEnvironmentValue + 295, // 229: openshell.v1.ProviderEnvironmentSnapshot.dynamic_credentials:type_name -> openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry + 4, // 230: openshell.v1.ProviderEnvironmentSnapshot.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason + 296, // 231: openshell.v1.ProviderEnvironmentSnapshot.files:type_name -> openshell.v1.ProviderEnvironmentSnapshot.FilesEntry + 305, // 232: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration + 306, // 233: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 303, // 234: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 311, // 235: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue + 171, // 236: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation + 297, // 237: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry + 172, // 238: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule + 173, // 239: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint + 174, // 240: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule + 176, // 241: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules + 177, // 242: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules + 178, // 243: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary + 312, // 244: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 310, // 245: openshell.v1.L7RuleTarget.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 313, // 246: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 175, // 247: openshell.v1.AddDenyRules.target:type_name -> openshell.v1.L7RuleTarget + 314, // 248: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule + 175, // 249: openshell.v1.AddAllowRules.target:type_name -> openshell.v1.L7RuleTarget + 298, // 250: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry + 306, // 251: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 189, // 252: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision + 306, // 253: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 189, // 254: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision + 13, // 255: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus + 12, // 256: openshell.v1.SandboxConfigurationAdmission.state:type_name -> openshell.v1.ConfigurationAdmissionState + 186, // 257: openshell.v1.ReportSandboxConfigurationRequest.admission:type_name -> openshell.v1.SandboxConfigurationAdmission + 13, // 258: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus + 301, // 259: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp + 301, // 260: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp + 303, // 261: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 299, // 262: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry + 306, // 263: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 301, // 264: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp + 116, // 265: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine + 116, // 266: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine + 196, // 267: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello + 204, // 268: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat + 221, // 269: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult + 222, // 270: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose + 200, // 271: openshell.v1.SupervisorMessage.config_update_result:type_name -> openshell.v1.ConfigUpdateResult + 197, // 272: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted + 202, // 273: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected + 205, // 274: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat + 210, // 275: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen + 222, // 276: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose + 203, // 277: openshell.v1.GatewayMessage.session_redirect:type_name -> openshell.v1.SessionRedirect + 198, // 278: openshell.v1.GatewayMessage.config_update:type_name -> openshell.v1.ConfigUpdate + 198, // 279: openshell.v1.SessionAccepted.bootstrap:type_name -> openshell.v1.ConfigUpdate + 305, // 280: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration + 315, // 281: openshell.v1.ConfigUpdate.sandbox_config:type_name -> openshell.sandbox.v1.SandboxConfigSnapshot + 167, // 282: openshell.v1.ConfigUpdate.provider_environment:type_name -> openshell.v1.ProviderEnvironmentSnapshot + 6, // 283: openshell.v1.ConfigUpdateResult.outcome:type_name -> openshell.v1.ConfigApplyOutcome + 199, // 284: openshell.v1.ConfigUpdateResult.failure:type_name -> openshell.v1.ConfigApplyFailure + 186, // 285: openshell.v1.ConfigUpdateResult.admission:type_name -> openshell.v1.SandboxConfigurationAdmission + 201, // 286: openshell.v1.ConfigUpdateResult.policy_load:type_name -> openshell.v1.PolicyLoadResult + 211, // 287: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget + 212, // 288: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget + 213, // 289: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit + 210, // 290: openshell.v1.PeerRelayInit.relay_open:type_name -> openshell.v1.RelayOpen + 215, // 291: openshell.v1.PeerRelayFrame.init:type_name -> openshell.v1.PeerRelayInit + 217, // 292: openshell.v1.PeerNotifyConfigUpdateRequest.sandbox:type_name -> openshell.v1.PeerConfigSandboxTarget + 218, // 293: openshell.v1.PeerNotifyConfigUpdateRequest.provider:type_name -> openshell.v1.PeerConfigProviderTarget + 301, // 294: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp + 301, // 295: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp + 223, // 296: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample + 225, // 297: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount + 312, // 298: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 301, // 299: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp + 301, // 300: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp + 301, // 301: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp + 301, // 302: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp + 303, // 303: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 303, // 304: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 306, // 305: openshell.v1.SubmitPolicyAnalysisRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 224, // 306: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary + 227, // 307: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk + 226, // 308: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary + 306, // 309: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 227, // 310: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk + 301, // 311: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp + 306, // 312: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 313: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 314: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 237, // 315: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval + 306, // 316: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 312, // 317: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 306, // 318: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 319: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 320: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 301, // 321: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp + 247, // 322: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry + 300, // 323: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry + 316, // 324: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 316, // 325: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 316, // 326: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace + 18, // 327: openshell.v1.DeleteWorkspaceResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 302, // 328: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 15, // 329: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole + 306, // 330: openshell.v1.AddWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 15, // 331: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole + 257, // 332: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember + 306, // 333: openshell.v1.RemoveWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 18, // 334: openshell.v1.RemoveWorkspaceMemberResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 306, // 335: openshell.v1.ListWorkspaceMembersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 257, // 336: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember + 301, // 337: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp + 19, // 338: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult + 273, // 339: openshell.v1.EndpointObservation.failure:type_name -> openshell.v1.EndpointFailure + 265, // 340: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation + 267, // 341: openshell.v1.ReportEndpointStatusRequest.token_grant_status:type_name -> openshell.v1.TokenGrantStatus + 268, // 342: openshell.v1.TokenGrantStatus.last_failure:type_name -> openshell.v1.TokenGrantFailureObservation + 273, // 343: openshell.v1.TokenGrantFailureObservation.failure:type_name -> openshell.v1.EndpointFailure + 19, // 344: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult + 301, // 345: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp + 273, // 346: openshell.v1.EndpointStatus.last_failure:type_name -> openshell.v1.EndpointFailure + 301, // 347: openshell.v1.SandboxProvisioning.configuration_change_time:type_name -> google.protobuf.Timestamp + 301, // 348: openshell.v1.SandboxProvisioning.first_rejection_time:type_name -> google.protobuf.Timestamp + 301, // 349: openshell.v1.SandboxProvisioning.deadline:type_name -> google.protobuf.Timestamp + 301, // 350: openshell.v1.SandboxProvisioning.timeout_time:type_name -> google.protobuf.Timestamp + 301, // 351: openshell.v1.SandboxProvisioning.cleanup_completed_time:type_name -> google.protobuf.Timestamp + 301, // 352: openshell.v1.SandboxProvisioning.cleanup_retry_time:type_name -> google.protobuf.Timestamp + 301, // 353: openshell.v1.SandboxProvisioning.attachment_change_time:type_name -> google.protobuf.Timestamp + 301, // 354: openshell.v1.SandboxProvisioning.preparation_deadline:type_name -> google.protobuf.Timestamp + 301, // 355: openshell.v1.SandboxProvisioning.admission_start_time:type_name -> google.protobuf.Timestamp + 20, // 356: openshell.v1.SandboxServiceExposure.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode + 21, // 357: openshell.v1.EndpointFailure.domain:type_name -> openshell.v1.EndpointFailureDomain + 16, // 358: openshell.v1.EndpointFailure.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction + 121, // 359: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry.value:type_name -> openshell.v1.ProviderCredentialValue + 301, // 360: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 301, // 361: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 135, // 362: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 164, // 363: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding + 135, // 364: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 26, // 365: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest + 28, // 366: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest + 30, // 367: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest + 54, // 368: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest + 65, // 369: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest + 67, // 370: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest + 68, // 371: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest + 58, // 372: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest + 59, // 373: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest + 60, // 374: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest + 61, // 375: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest + 69, // 376: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest + 70, // 377: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest + 71, // 378: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest + 87, // 379: openshell.v1.OpenShell.GetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest + 72, // 380: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest + 73, // 381: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest + 74, // 382: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest + 92, // 383: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest + 94, // 384: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest + 95, // 385: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest + 96, // 386: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest + 98, // 387: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest + 102, // 388: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest + 104, // 389: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest + 110, // 390: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame + 111, // 391: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput + 118, // 392: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest + 119, // 393: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest + 120, // 394: openshell.v1.OpenShell.GetProviderCredentials:input_type -> openshell.v1.GetProviderCredentialsRequest + 123, // 395: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest + 128, // 396: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest + 129, // 397: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest + 153, // 398: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest + 155, // 399: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest + 157, // 400: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest + 124, // 401: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest + 141, // 402: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest + 143, // 403: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest + 145, // 404: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest + 147, // 405: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest + 125, // 406: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest + 160, // 407: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest + 317, // 408: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest + 318, // 409: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest + 170, // 410: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest + 180, // 411: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest + 182, // 412: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest + 184, // 413: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest + 266, // 414: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest + 89, // 415: openshell.v1.OpenShell.ReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest + 187, // 416: openshell.v1.OpenShell.ReportSandboxConfiguration:input_type -> openshell.v1.ReportSandboxConfigurationRequest + 162, // 417: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest + 168, // 418: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest + 190, // 419: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest + 191, // 420: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest + 194, // 421: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage + 206, // 422: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest + 208, // 423: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest + 214, // 424: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame + 216, // 425: openshell.v1.OpenShell.PeerRelay:input_type -> openshell.v1.PeerRelayFrame + 89, // 426: openshell.v1.OpenShell.PeerReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest + 266, // 427: openshell.v1.OpenShell.PeerReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest + 87, // 428: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest + 219, // 429: openshell.v1.OpenShell.PeerNotifyConfigUpdate:input_type -> openshell.v1.PeerNotifyConfigUpdateRequest + 114, // 430: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest + 229, // 431: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest + 231, // 432: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest + 233, // 433: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest + 235, // 434: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest + 238, // 435: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest + 240, // 436: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest + 242, // 437: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest + 244, // 438: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest + 246, // 439: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest + 22, // 440: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest + 24, // 441: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest + 249, // 442: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest + 251, // 443: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest + 253, // 444: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest + 255, // 445: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest + 258, // 446: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest + 260, // 447: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest + 262, // 448: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest + 55, // 449: openshell.v1.SandboxUpdates.UpdateSandbox:input_type -> openshell.v1.UpdateSandboxRequest + 27, // 450: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse + 29, // 451: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse + 31, // 452: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse + 75, // 453: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse + 66, // 454: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse + 75, // 455: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse + 76, // 456: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse + 62, // 457: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 62, // 458: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 63, // 459: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse + 64, // 460: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse + 77, // 461: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse + 78, // 462: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse + 79, // 463: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse + 88, // 464: openshell.v1.OpenShell.GetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse + 91, // 465: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse + 75, // 466: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse + 75, // 467: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse + 93, // 468: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse + 101, // 469: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse + 101, // 470: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse + 97, // 471: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse + 99, // 472: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse + 103, // 473: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse + 108, // 474: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent + 110, // 475: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame + 108, // 476: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent + 126, // 477: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse + 126, // 478: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse + 122, // 479: openshell.v1.OpenShell.GetProviderCredentials:output_type -> openshell.v1.GetProviderCredentialsResponse + 127, // 480: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse + 152, // 481: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse + 151, // 482: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse + 154, // 483: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse + 156, // 484: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse + 158, // 485: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse + 126, // 486: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse + 142, // 487: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse + 144, // 488: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse + 146, // 489: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse + 148, // 490: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse + 159, // 491: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse + 161, // 492: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse + 319, // 493: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse + 320, // 494: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse + 179, // 495: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse + 181, // 496: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse + 183, // 497: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse + 185, // 498: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse + 269, // 499: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse + 90, // 500: openshell.v1.OpenShell.ReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse + 188, // 501: openshell.v1.OpenShell.ReportSandboxConfiguration:output_type -> openshell.v1.ReportSandboxConfigurationResponse + 165, // 502: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse + 169, // 503: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse + 193, // 504: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse + 192, // 505: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse + 195, // 506: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage + 207, // 507: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse + 209, // 508: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse + 214, // 509: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame + 216, // 510: openshell.v1.OpenShell.PeerRelay:output_type -> openshell.v1.PeerRelayFrame + 90, // 511: openshell.v1.OpenShell.PeerReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse + 269, // 512: openshell.v1.OpenShell.PeerReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse + 88, // 513: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse + 220, // 514: openshell.v1.OpenShell.PeerNotifyConfigUpdate:output_type -> openshell.v1.PeerNotifyConfigUpdateResponse + 115, // 515: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent + 230, // 516: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse + 232, // 517: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse + 234, // 518: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse + 236, // 519: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse + 239, // 520: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse + 241, // 521: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse + 243, // 522: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse + 245, // 523: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse + 248, // 524: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse + 23, // 525: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse + 25, // 526: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse + 250, // 527: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse + 252, // 528: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse + 254, // 529: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse + 256, // 530: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse + 259, // 531: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse + 261, // 532: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse + 263, // 533: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse + 75, // 534: openshell.v1.SandboxUpdates.UpdateSandbox:output_type -> openshell.v1.SandboxResponse + 450, // [450:535] is the sub-list for method output_type + 365, // [365:450] is the sub-list for method input_type + 365, // [365:365] is the sub-list for extension type_name + 365, // [365:365] is the sub-list for extension extendee + 0, // [0:365] is the sub-list for field type_name } func init() { file_openshell_proto_init() } @@ -22064,37 +22307,38 @@ func file_openshell_proto_init() { file_openshell_proto_msgTypes[20].OneofWrappers = []any{} file_openshell_proto_msgTypes[21].OneofWrappers = []any{} file_openshell_proto_msgTypes[29].OneofWrappers = []any{} - file_openshell_proto_msgTypes[56].OneofWrappers = []any{ + file_openshell_proto_msgTypes[33].OneofWrappers = []any{} + file_openshell_proto_msgTypes[59].OneofWrappers = []any{ (*ConfigSnapshotRevision_SandboxConfig)(nil), (*ConfigSnapshotRevision_ProviderEnvironment)(nil), (*ConfigSnapshotRevision_ProviderTarget)(nil), } - file_openshell_proto_msgTypes[83].OneofWrappers = []any{ + file_openshell_proto_msgTypes[86].OneofWrappers = []any{ (*ExecSandboxEvent_Stdout)(nil), (*ExecSandboxEvent_Stderr)(nil), (*ExecSandboxEvent_Exit)(nil), } - file_openshell_proto_msgTypes[84].OneofWrappers = []any{ + file_openshell_proto_msgTypes[87].OneofWrappers = []any{ (*TcpForwardInit_Ssh)(nil), (*TcpForwardInit_Tcp)(nil), } - file_openshell_proto_msgTypes[85].OneofWrappers = []any{ + file_openshell_proto_msgTypes[88].OneofWrappers = []any{ (*TcpForwardFrame_Init)(nil), (*TcpForwardFrame_Data)(nil), } - file_openshell_proto_msgTypes[86].OneofWrappers = []any{ + file_openshell_proto_msgTypes[89].OneofWrappers = []any{ (*ExecSandboxInput_Start)(nil), (*ExecSandboxInput_Stdin)(nil), (*ExecSandboxInput_Resize)(nil), } - file_openshell_proto_msgTypes[90].OneofWrappers = []any{ + file_openshell_proto_msgTypes[93].OneofWrappers = []any{ (*SandboxStreamEvent_Sandbox)(nil), (*SandboxStreamEvent_Log)(nil), (*SandboxStreamEvent_Event)(nil), (*SandboxStreamEvent_Warning)(nil), (*SandboxStreamEvent_DraftPolicyUpdate)(nil), } - file_openshell_proto_msgTypes[146].OneofWrappers = []any{ + file_openshell_proto_msgTypes[149].OneofWrappers = []any{ (*PolicyMergeOperation_AddRule)(nil), (*PolicyMergeOperation_RemoveEndpoint)(nil), (*PolicyMergeOperation_RemoveRule)(nil), @@ -22102,15 +22346,15 @@ func file_openshell_proto_init() { (*PolicyMergeOperation_AddAllowRules)(nil), (*PolicyMergeOperation_RemoveBinary)(nil), } - file_openshell_proto_msgTypes[150].OneofWrappers = []any{} - file_openshell_proto_msgTypes[169].OneofWrappers = []any{ + file_openshell_proto_msgTypes[153].OneofWrappers = []any{} + file_openshell_proto_msgTypes[172].OneofWrappers = []any{ (*SupervisorMessage_Hello)(nil), (*SupervisorMessage_Heartbeat)(nil), (*SupervisorMessage_RelayOpenResult)(nil), (*SupervisorMessage_RelayClose)(nil), (*SupervisorMessage_ConfigUpdateResult)(nil), } - file_openshell_proto_msgTypes[170].OneofWrappers = []any{ + file_openshell_proto_msgTypes[173].OneofWrappers = []any{ (*GatewayMessage_SessionAccepted)(nil), (*GatewayMessage_SessionRejected)(nil), (*GatewayMessage_Heartbeat)(nil), @@ -22119,34 +22363,34 @@ func file_openshell_proto_init() { (*GatewayMessage_SessionRedirect)(nil), (*GatewayMessage_ConfigUpdate)(nil), } - file_openshell_proto_msgTypes[185].OneofWrappers = []any{ + file_openshell_proto_msgTypes[188].OneofWrappers = []any{ (*RelayOpen_Ssh)(nil), (*RelayOpen_Tcp)(nil), } - file_openshell_proto_msgTypes[189].OneofWrappers = []any{ + file_openshell_proto_msgTypes[192].OneofWrappers = []any{ (*RelayFrame_Init)(nil), (*RelayFrame_Data)(nil), } - file_openshell_proto_msgTypes[191].OneofWrappers = []any{ + file_openshell_proto_msgTypes[194].OneofWrappers = []any{ (*PeerRelayFrame_Init)(nil), (*PeerRelayFrame_Data)(nil), } - file_openshell_proto_msgTypes[194].OneofWrappers = []any{ + file_openshell_proto_msgTypes[197].OneofWrappers = []any{ (*PeerNotifyConfigUpdateRequest_Sandbox)(nil), (*PeerNotifyConfigUpdateRequest_Workspace)(nil), (*PeerNotifyConfigUpdateRequest_AllConnected)(nil), (*PeerNotifyConfigUpdateRequest_Provider)(nil), } - file_openshell_proto_msgTypes[248].OneofWrappers = []any{} + file_openshell_proto_msgTypes[251].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_openshell_proto_rawDesc), len(file_openshell_proto_rawDesc)), NumEnums: 22, - NumMessages: 275, + NumMessages: 279, NumExtensions: 0, - NumServices: 1, + NumServices: 2, }, GoTypes: file_openshell_proto_goTypes, DependencyIndexes: file_openshell_proto_depIdxs, diff --git a/sdk/go/proto/openshellv1/openshell_grpc.pb.go b/sdk/go/proto/openshellv1/openshell_grpc.pb.go index 57f17c466b..4146234a5d 100644 --- a/sdk/go/proto/openshellv1/openshell_grpc.pb.go +++ b/sdk/go/proto/openshellv1/openshell_grpc.pb.go @@ -3551,3 +3551,119 @@ var OpenShell_ServiceDesc = grpc.ServiceDesc{ }, Metadata: "openshell.proto", } + +const ( + SandboxUpdates_UpdateSandbox_FullMethodName = "/openshell.v1.SandboxUpdates/UpdateSandbox" +) + +// SandboxUpdatesClient is the client API for SandboxUpdates service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// Optional sandbox mutations are separate from OpenShell so existing generated +// OpenShell server implementations remain source-compatible. +type SandboxUpdatesClient interface { + // Update the workload image, command, environment, labels, or annotations of a + // stopped sandbox while preserving its identity and retained storage. Requires + // compute-driver support for the selected changes and explicit gateway activation + // of sandbox updates. + UpdateSandbox(ctx context.Context, in *UpdateSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) +} + +type sandboxUpdatesClient struct { + cc grpc.ClientConnInterface +} + +func NewSandboxUpdatesClient(cc grpc.ClientConnInterface) SandboxUpdatesClient { + return &sandboxUpdatesClient{cc} +} + +func (c *sandboxUpdatesClient) UpdateSandbox(ctx context.Context, in *UpdateSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SandboxResponse) + err := c.cc.Invoke(ctx, SandboxUpdates_UpdateSandbox_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// SandboxUpdatesServer is the server API for SandboxUpdates service. +// All implementations must embed UnimplementedSandboxUpdatesServer +// for forward compatibility. +// +// Optional sandbox mutations are separate from OpenShell so existing generated +// OpenShell server implementations remain source-compatible. +type SandboxUpdatesServer interface { + // Update the workload image, command, environment, labels, or annotations of a + // stopped sandbox while preserving its identity and retained storage. Requires + // compute-driver support for the selected changes and explicit gateway activation + // of sandbox updates. + UpdateSandbox(context.Context, *UpdateSandboxRequest) (*SandboxResponse, error) + mustEmbedUnimplementedSandboxUpdatesServer() +} + +// UnimplementedSandboxUpdatesServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedSandboxUpdatesServer struct{} + +func (UnimplementedSandboxUpdatesServer) UpdateSandbox(context.Context, *UpdateSandboxRequest) (*SandboxResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateSandbox not implemented") +} +func (UnimplementedSandboxUpdatesServer) mustEmbedUnimplementedSandboxUpdatesServer() {} +func (UnimplementedSandboxUpdatesServer) testEmbeddedByValue() {} + +// UnsafeSandboxUpdatesServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to SandboxUpdatesServer will +// result in compilation errors. +type UnsafeSandboxUpdatesServer interface { + mustEmbedUnimplementedSandboxUpdatesServer() +} + +func RegisterSandboxUpdatesServer(s grpc.ServiceRegistrar, srv SandboxUpdatesServer) { + // If the following call panics, it indicates UnimplementedSandboxUpdatesServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&SandboxUpdates_ServiceDesc, srv) +} + +func _SandboxUpdates_UpdateSandbox_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateSandboxRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SandboxUpdatesServer).UpdateSandbox(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SandboxUpdates_UpdateSandbox_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SandboxUpdatesServer).UpdateSandbox(ctx, req.(*UpdateSandboxRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// SandboxUpdates_ServiceDesc is the grpc.ServiceDesc for SandboxUpdates service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var SandboxUpdates_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "openshell.v1.SandboxUpdates", + HandlerType: (*SandboxUpdatesServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "UpdateSandbox", + Handler: _SandboxUpdates_UpdateSandbox_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "openshell.proto", +} diff --git a/sdk/go/tests/compatibility/sandbox_updates_test.go b/sdk/go/tests/compatibility/sandbox_updates_test.go new file mode 100644 index 0000000000..44bc7b023f --- /dev/null +++ b/sdk/go/tests/compatibility/sandbox_updates_test.go @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package compatibility_test + +import ( + "context" + "net" + "testing" + + dm "github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1" + pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +func TestSandboxStatusRetainsBaselinePublicShape(t *testing.T) { + // Freeze the pre-image-update fields and tags: private operation state must + // not enlarge the public Rust struct or leak into generated SDK messages. + fields := []string{ + "agent_pod", "agent_fd", "sandbox_fd", "conditions", "phase", + "current_policy_version", "main_process_instance_id", "exit_code", + "endpoint_statuses", "configuration_admission", "configuration_activated", + "provisioning", "restart_count", "next_restart_time", + "main_process_started_time", "last_token_grant_failure", + } + descriptor := (&pb.SandboxStatus{}).ProtoReflect().Descriptor() + require.Equal(t, len(fields), descriptor.Fields().Len()) + for i, name := range fields { + field := descriptor.Fields().ByName(protoreflect.Name(name)) + require.NotNil(t, field, name) + require.Equal(t, protoreflect.FieldNumber(i+2), field.Number(), name) + } + require.Nil(t, pb.File_openshell_proto.Messages().ByName("SandboxImageUpdate")) + legacy := pb.File_openshell_proto.Services().ByName("OpenShell") + require.Nil(t, legacy.Methods().ByName("UpdateSandbox")) + updates := pb.File_openshell_proto.Services().ByName("SandboxUpdates") + require.NotNil(t, updates) + require.Equal(t, 1, updates.Methods().Len()) + method := updates.Methods().Get(0) + require.Equal(t, protoreflect.FullName("openshell.v1.UpdateSandboxRequest"), method.Input().FullName()) + require.Equal(t, protoreflect.FullName("openshell.v1.SandboxResponse"), method.Output().FullName()) + require.Equal(t, "/openshell.v1.SandboxUpdates/UpdateSandbox", pb.SandboxUpdates_UpdateSandbox_FullMethodName) +} + +type legacyHealthServer struct { + pb.UnimplementedOpenShellServer +} + +func (legacyHealthServer) Health(context.Context, *pb.HealthRequest) (*pb.HealthResponse, error) { + return &pb.HealthResponse{Version: "legacy-compatible"}, nil +} + +type imageUpdateServer struct { + pb.UnimplementedSandboxUpdatesServer + request *pb.UpdateSandboxRequest +} + +func (s *imageUpdateServer) UpdateSandbox(_ context.Context, request *pb.UpdateSandboxRequest) (*pb.SandboxResponse, error) { + s.request = proto.Clone(request).(*pb.UpdateSandboxRequest) + return &pb.SandboxResponse{Sandbox: &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "stable-id", Name: request.Name, Workspace: request.WorkspaceScope.GetWorkspace()}, + Spec: &pb.SandboxSpec{Template: &pb.SandboxTemplate{Image: request.GetImage()}}, + }}, nil +} + +func TestSeparateSandboxUpdatesTransportCoexistsWithLegacyOpenShell(t *testing.T) { + listener := bufconn.Listen(1024 * 1024) + server := grpc.NewServer(grpc.UnaryInterceptor(func(ctx context.Context, request any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + if info.FullMethod == pb.SandboxUpdates_UpdateSandbox_FullMethodName { + md, _ := metadata.FromIncomingContext(ctx) + if len(md.Get("authorization")) == 0 { + return nil, status.Error(codes.Unauthenticated, "bearer required") + } + } + return handler(ctx, request) + })) + updatesServer := &imageUpdateServer{} + pb.RegisterOpenShellServer(server, legacyHealthServer{}) + pb.RegisterSandboxUpdatesServer(server, updatesServer) + go func() { _ = server.Serve(listener) }() + t.Cleanup(server.Stop) + t.Cleanup(func() { _ = listener.Close() }) + conn, err := grpc.NewClient("passthrough:///sandbox-updates", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return listener.Dial() + })) + require.NoError(t, err) + t.Cleanup(func() { _ = conn.Close() }) + legacy := pb.NewOpenShellClient(conn) + health, err := legacy.Health(context.Background(), &pb.HealthRequest{}) + require.NoError(t, err) + require.Equal(t, "legacy-compatible", health.Version) + updates := pb.NewSandboxUpdatesClient(conn) + request := &pb.UpdateSandboxRequest{ + WorkspaceScope: &dm.WorkspaceSelector{Selection: &dm.WorkspaceSelector_Workspace{Workspace: "team"}}, + Name: "sandbox", + Image: proto.String("example/workload:new"), + ExpectedResourceVersion: 42, + RequestId: "550e8400-e29b-41d4-a716-446655440000", + } + _, err = updates.UpdateSandbox(context.Background(), request) + require.Equal(t, codes.Unauthenticated, status.Code(err)) + require.Nil(t, updatesServer.request) + ctx := metadata.NewOutgoingContext(context.Background(), metadata.Pairs("authorization", "Bearer test-token")) + response, err := updates.UpdateSandbox(ctx, request) + require.NoError(t, err) + require.True(t, proto.Equal(request, updatesServer.request)) + require.Equal(t, "stable-id", response.Sandbox.Metadata.Id) + require.Equal(t, "team", response.Sandbox.Metadata.Workspace) + require.Equal(t, request.GetImage(), response.Sandbox.Spec.Template.Image) + // The old service has no accidental update endpoint. + err = conn.Invoke(ctx, "/openshell.v1.OpenShell/UpdateSandbox", request, &pb.SandboxResponse{}) + require.Equal(t, codes.Unimplemented, status.Code(err)) +} diff --git a/sdk/typescript/src/raw.test.ts b/sdk/typescript/src/raw.test.ts index 9d2e02ac6e..798d23b536 100644 --- a/sdk/typescript/src/raw.test.ts +++ b/sdk/typescript/src/raw.test.ts @@ -3,10 +3,55 @@ import type { MessageInitShape } from '@bufbuild/protobuf'; import { timestampFromDate } from '@bufbuild/protobuf/wkt'; -import { createRouterTransport } from '@connectrpc/connect'; +import { createClient, createRouterTransport } from '@connectrpc/connect'; import { describe, expect, it } from 'vitest'; import { SandboxClient } from './index.js'; -import { EndpointResult, type EndpointStatusSchema, OpenShell, SandboxPhase } from './raw.js'; +import { EndpointResult, type EndpointStatusSchema, OpenShell, SandboxPhase, SandboxUpdates } from './raw.js'; + +describe('raw sandbox image updates', () => { + it.each([true, false])('uses a separate service on the existing transport (binary: %s)', async (useBinaryFormat) => { + const transport = createRouterTransport( + (router) => { + router.service(OpenShell, { + getSandbox: () => ({ sandbox: { metadata: { id: 'stable-id' } } }), + }); + router.service(SandboxUpdates, { + updateSandbox: (request) => { + expect(request.workspaceScope?.selection).toEqual({ case: 'workspace', value: 'team' }); + expect(request.name).toBe('sandbox'); + expect(request.expectedResourceVersion).toBe(42n); + expect(request.requestId).toBe('550e8400-e29b-41d4-a716-446655440000'); + return { + sandbox: { + metadata: { id: 'stable-id', name: request.name, workspace: 'team' }, + spec: { template: { image: request.image } }, + status: { phase: SandboxPhase.STOPPED }, + }, + }; + }, + }); + }, + { transport: { useBinaryFormat } }, + ); + const legacy = new SandboxClient(transport); + const updates = createClient(SandboxUpdates, legacy.transport); + expect(Object.hasOwn(OpenShell.method, 'updateSandbox')).toBe(false); + expect(SandboxUpdates.typeName).toBe('openshell.v1.SandboxUpdates'); + const before = await legacy.raw.getSandbox({ name: 'sandbox' }); + const response = await updates.updateSandbox({ + workspaceScope: { selection: { case: 'workspace', value: 'team' } }, + name: 'sandbox', + image: 'example/workload:new', + expectedResourceVersion: 42n, + requestId: '550e8400-e29b-41d4-a716-446655440000', + }); + expect(response.sandbox?.metadata?.id).toBe(before.sandbox?.metadata?.id); + expect(response.sandbox?.spec?.template?.image).toBe('example/workload:new'); + expect(response.sandbox?.status?.phase).toBe(SandboxPhase.STOPPED); + expect(response.sandbox?.status).not.toHaveProperty('imageUpdate'); + expect(response.sandbox?.status).not.toHaveProperty('imageRevision'); + }); +}); describe('raw sandbox endpoint status', () => { it.each([true, false])( diff --git a/sdk/typescript/src/raw.ts b/sdk/typescript/src/raw.ts index 1571e8e23e..067a6558b9 100644 --- a/sdk/typescript/src/raw.ts +++ b/sdk/typescript/src/raw.ts @@ -4,7 +4,7 @@ export * from './gen/datamodel_pb.js'; export * from './gen/google/rpc/error_details_pb.js'; // Advanced surface: the full generated protobuf types (messages, enums, and the -// OpenShell service descriptor) for callers using the raw escape hatch on +// OpenShell and SandboxUpdates service descriptors) for callers using the raw escape hatch on // OpenShellClient / SandboxClient (`.raw` and `.transport`). These are the // uncurated wire types; import them from '@nvidia/openshell-sdk/raw'. The // curated entry point stays free of generated types so its surface does not diff --git a/skills/debug-openshell-cluster/SKILL.md b/skills/debug-openshell-cluster/SKILL.md index 40351c998e..ff09ef7899 100644 --- a/skills/debug-openshell-cluster/SKILL.md +++ b/skills/debug-openshell-cluster/SKILL.md @@ -1206,6 +1206,46 @@ network supervisor logs for CONNECT and upstream-proxy decisions: kubectl -n logs pod/ --tail=200 | grep -Ei 'upstream|connect|proxy' ``` +#### Sandbox image-update activation and recovery + +`openshell.v1.SandboxUpdates/UpdateSandbox` uses the existing authenticated +gateway listener and defaults to disabled. Check the effective +`[openshell.gateway] enable_sandbox_updates` value and its overrides, +`--enable-sandbox-updates true` or +`OPENSHELL_ENABLE_SANDBOX_UPDATES=true`. Canonical Helm values use: + +```yaml +gatewayConfig: + "openshell.gateway": + enable_sandbox_updates: true +``` + +Do not enable the flag to work around an error. It attests that all incompatible +gateways, including unregistered processes and Jobs, have been drained and +retired, their outstanding driver operations have settled, and old binaries +cannot return through rollout or rollback configurations. Keep it false during +a rolling upgrade. SQLite supports one gateway process, not shared-file HA. + +On PostgreSQL, an unmarked client backend in the same database blocks updates, +including an administrative session. The new binary sets every pool +connection's `application_name` to `openshell-image-update-v2`; close unmarked +sessions or use a dedicated database, rather than relabeling old clients. Live +gateway membership must advertise image-update protocol 2; malformed or +truncated membership reads fail closed. Neither these observations nor an +expired membership TTL proves old gateways and their work have stopped. Follow +the published +[activation checklist](https://docs.nvidia.com/openshell/latest/kubernetes/high-availability#activate-sandbox-updates), +and do not downgrade while updates are pending or retained sandboxes require +the private revision protocol. + +For an operation-pending error, use the original selected fields and target values and a fresh +sandbox resource version to reconcile through `SandboxUpdates`. Intent and +revision are private, not new public status fields. Start, stop, provisioning +retry, and delete do not bypass the barrier. Preserve request-admission semantics: +never change the payload under an existing ID or blindly switch IDs after a +timeout. See the published +[recovery contract](https://docs.nvidia.com/openshell/latest/sdk/api-errors#sandbox-image-update-recovery). + ### Step 7: Check VM-Backed Gateways Use the VM driver logs and host diagnostics available in the user's environment. Verify: diff --git a/skills/openshell-cli/SKILL.md b/skills/openshell-cli/SKILL.md index 84e6d65a2c..034fdf5638 100644 --- a/skills/openshell-cli/SKILL.md +++ b/skills/openshell-cli/SKILL.md @@ -536,6 +536,48 @@ for verification behavior and release compatibility. For an infrastructure `Error` on a backend that supports restarting stopped sandboxes, fix the reported cause, run `sandbox stop`, and wait for it to succeed before running `sandbox start`. This retains the sandbox and persistent workspace but cannot recover deleted resources or storage. For a provisioning timeout, wait for cleanup to finish, then run `sandbox start` directly. +### Change a stopped sandbox's workload image + +Do not invent a `sandbox update` command or use delete/create to change an +existing sandbox's configuration. The separate +`openshell.v1.SandboxUpdates/UpdateSandbox` RPC uses the same gateway listener +and authentication as `OpenShell`. It defaults to disabled; the operator must +complete the published +[activation checklist](https://docs.nvidia.com/openshell/latest/kubernetes/high-availability#activate-sandbox-updates) +before enabling `enable_sandbox_updates`. Keep it false during a rolling +upgrade and never enable it merely to bypass a failed compatibility check. +After activation, the RPC initially supports only stopped Kubernetes sandboxes; +other drivers return `UNIMPLEMENTED`. Stop and +wait for `Stopped`, read the sandbox's current resource version through +`GetSandbox`, call raw `UpdateSandbox` with the named workspace, sandbox name, +selected image, command, environment overrides, labels or annotations, and required nonzero `expected_resource_version`, then start after +success. Rust SDK callers use `client.raw_sandbox_updates_fresh()`; generated +clients use `SandboxUpdates`, not the unchanged `OpenShell` service. There is no +curated SDK update method. The sandbox identity, Kubernetes +custom resource, and persistent volume claim remain; the existing workspace is +not reseeded and trusted runtime images do not change. Omitted update fields are +unchanged; supplied lists and maps replace their user-owned values, including +explicitly empty values. Template environment defaults still apply. System metadata +is protected; top-level labels and annotations do not update pod metadata. +Combined configuration changes require the configuration-update driver capability +and a uniformly upgraded protocol-2 gateway deployment. + +For an image that fails to start and reaches a provisioning timeout, wait for +cleanup completion and settlement of the original driver operation, then run +`sandbox stop` to obtain `Stopped` before correcting the image. This retains the +timeout diagnostic and storage and does not bypass pending cleanup. + +An operation-pending error after a failure blocks start, stop, provisioning +retry, and delete. Image intent and revision are private; the public `SandboxStatus` +shape is unchanged. Read a fresh version and retry the same selected fields and target values originally +requested, even if the spec already names it. An optional +`request_id` with an unresolved admission does not execute again; inspect and +reconcile state before submitting a new conditional request with a fresh +version and new ID, or no ID. Never change the payload under the original ID or +blindly switch IDs after a timeout. See the published +[image update workflow](https://docs.nvidia.com/openshell/latest/how-it-works/sandboxes/overview#update-the-image-of-a-stopped-sandbox) +and [API errors](https://docs.nvidia.com/openshell/latest/sdk/api-errors). + --- ## Workflow 4: Policy Iteration Loop