diff --git a/Cargo.lock b/Cargo.lock index 6b94d27787..5fa556505f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4159,6 +4159,7 @@ dependencies = [ "rcgen", "serde", "serde_json", + "sha2 0.10.9", "temp-env", "thiserror 2.0.18", "tokio", diff --git a/architecture/compute-runtimes.md b/architecture/compute-runtimes.md index b9932615d6..872148a563 100644 --- a/architecture/compute-runtimes.md +++ b/architecture/compute-runtimes.md @@ -400,6 +400,66 @@ credentials. The driver removes its scheduling gate only after the companions exist; measured confirmation and supervisor-session registration gate public readiness. +Kubernetes supervisor startup uses `RegisterSupervisor` before any tenant RPC. +The driver validates the projected token, live proxy/workload Pods and Sandbox +ownership, and returns the prepared runtime coordinates to the gateway. The +driver requires Secret read permission in sandbox namespaces to validate the +proxy descriptor and its owner UID during registration, plus list/delete +permission for generation Secret cleanup during stop and rollback. The +gateway checks the persisted runtime generation and delivers the operational +authentication bundle and launch configuration. Proxy Secrets retain physical +transport and proxy-CA material, but contain no operational auth bundle. + +The workload listener can wait without a logical sandbox ID. Its first token +must carry a signed binding matching all protected runtime resource claims; +only then does it pin the logical sandbox identity. Later tokens must match that +identity, generation and epoch. Transport retries preserve assignment and the +existing agent-launch replay protection. Other compute drivers retain their +file-provisioned authentication path. + +Optional Kubernetes warm pooling uses the same provisioning path. +The gateway periodically supplies complete template snapshots and public trust; +the driver elects a controller using a Kubernetes Lease. Templates from unavailable +operator workspaces are excluded without blocking snapshot refreshes for eligible +workspaces. Template startup hints set desired spare capacity, bounded by a +driver-wide spare-pair cap. Preparing and ready pairs count toward capacity. +A Kubernetes Sandbox owns each pair and +records its physical identity, preparation revision and state without a logical +sandbox ID. Pool inventory selects managed resources by gateway and pool labels; +it must not require the logical sandbox-ID label. Ordinary sandbox listing and lifecycle reconciliation exclude it. +An immutable parent-owned Secret journals both bootstrap Secrets so interrupted +preparation preserves TLS material. Recovery accepts an already released workload +only when its recorded Pod, Service and network-policy identities still match; +it requires the existing journal and validates both bootstrap Secrets before +finishing release. Generation cleanup also removes this journal. +The authentication generation is also used in Sandbox metadata and generation-scoped +resource names, so allocation and proxy registration refer to the same runtime. + +Standby readiness requires a local workload-listener probe, a live paired proxy +with recent authenticated registration, the boundary Service and network fence. +It does not mean the agent has started. The leader conditionally retires spare +pairs when preparation stalls, standby age expires, templates or trust change, +or pooling is disabled. Retirement competes with allocation through resource +versions. Claimed resources remain under ordinary lifecycle management. + +Template-based creation selects a compatible ready pair and persists its Sandbox +UID and prepared generation in the logical record before attempting a conditional +claim. The claim removes availability and records the logical ID and command on +the parent Sandbox. Registration checks that parent UID against the persisted +assignment before delivering credentials. Both Pods retain their physical identity; +stop/start and cleanup derive companion names from that identity. + +Uncertain claims remain pending against the same candidate across gateway restarts. +Recovery fences a rejected, still-unassigned candidate before recording another; +it adopts an already committed claim instead of retiring it. With no eligible +candidate, creation uses the cold path. Readiness and terminal main-process results +finalize claim recovery. Explicit restart also clears pending claim recovery +atomically with the new provisioning attempt, retaining the physical assignment +and its reservation so recovery cannot replace a restarting runtime's identity. +Pending deletion fences allocation before removing the logical record. Assigned +pairs are single-use and are deleted with the sandbox; template changes or removal +retire only spare inventory. + ## Images The gateway image and Helm chart are built from this repository. Sandbox images diff --git a/architecture/gateway.md b/architecture/gateway.md index a866e92578..f0bf275179 100644 --- a/architecture/gateway.md +++ b/architecture/gateway.md @@ -7,6 +7,10 @@ attachments; and asks compute runtimes to create or delete sandbox workloads. ## Responsibilities - Authenticate clients and sandbox supervisor sessions. +- Authorize Kubernetes proxy registration against driver-verified physical + runtime identity and persisted logical assignment. `RegisterSupervisor` + supplies initial credentials; it does not bypass generation checks or recover + a credential lineage that has already advanced through refresh. - Serve gRPC APIs for sandbox lifecycle, provider management, policy updates, settings, logs, watch streams, and relay forwarding. - Serve HTTP endpoints for health, WebSocket tunnels, and edge-auth flows. @@ -316,7 +320,9 @@ identity inspection without client-side token decoding. Sandbox secrets are gateway-signed JWTs bound to a single sandbox ID. Docker, Podman, and VM drivers deliver the initial token through supervisor-only runtime material; Kubernetes supervisors exchange a projected ServiceAccount -token through `IssueSandboxToken`. The gateway delegates that opaque credential +token through `RegisterSupervisor` to receive their runtime assignment and +in-memory authentication bundle. `IssueSandboxToken` remains available for +existing bootstrap clients. The gateway delegates that opaque credential to the selected compute driver's `AuthenticateSandbox` RPC. A capable driver is trusted to return the authenticated sandbox ID, while the gateway still requires a matching durable sandbox record before minting a JWT. The Kubernetes driver @@ -399,6 +405,11 @@ The storage schema is intentionally narrow: Public RPC contracts and durable protobuf formats have separate ownership. The `openshell.v1.OpenShell` service's request and response roots, streaming flags, and transitive message closure come from the public descriptor set generated by `openshell-core`. The `public_and_durable_schema_inventories_are_complete` test in `openshell-server` owns the counts and fingerprints and requires this inventory to be reviewed whenever it changes. Compute-driver, credential-driver, gateway-interceptor, and supervisor-middleware services are compiled contracts for internal extension boundaries. +`RegisterSupervisor` adds request and response messages to the public closure. +Its assignment response carries initial runtime credentials but is not persisted +as a protobuf storage root. Warm-pool selection and synchronization extend the +internal compute-driver contract; neither changes the durable schema inventory. + `ReportEndpointStatus` is a sandbox-authenticated public gateway RPC. Its request, response, and `EndpointObservation` messages belong only to the public closure. `EndpointStatus` and `EndpointResult` also belong to the durable closure because `Sandbox.status.endpoint_statuses` persists them. The repeated status field uses a new wire tag; stored sandboxes without it decode with an empty endpoint list and retain their lifecycle fields. A fixed payload encoded with the earlier sandbox schema verifies that no database rewrite is required. Allow and deny append requests carry `L7RuleTarget` to declare the rule, endpoint, and complete affected scope. The removed `host` and `port` fields remain reserved by number and name, and requests without a target are rejected. These mutation requests are not persisted formats. @@ -550,6 +561,20 @@ revisions commit in one database transaction. SQLite serializes this operation with an immediate transaction, while Postgres locks the sandbox row. A failed resource-version check or revision insert rolls back the entire operation. +Warm allocation intent and its exclusive target reservation commit in one +transaction. The `allocation_claims` table keys ownership by configured driver, +namespace, and physical UID; the owner is a sandbox provisioning attempt and +runtime generation. A uniqueness conflict rolls back the sandbox write and +allows immediate selection of another target. Reservations have no TTL and +survive ambiguous driver outcomes and gateway restart. Only fenced or reclaimed +allocations release ownership; deleting the owning sandbox cascades its claims. +The driver's resource CAS still arbitrates physical assignment and retirement. +Creates with attached providers hold the gateway-wide sandbox lock from provider +validation through the sandbox and reservation commit, including reservation +retries. They release it before submitting creation to the driver. Allocation +completion and recovery then acquire their own synchronization and lifecycle +locks. + SQLite is the default local store; Postgres is supported for deployments that need an external database or multi-replica coordination. Both backends expose the same `Store` API and the same logical schema. Backend differences stay diff --git a/architecture/sandbox.md b/architecture/sandbox.md index 2fec94297b..b5c08d102f 100644 --- a/architecture/sandbox.md +++ b/architecture/sandbox.md @@ -4,6 +4,26 @@ A sandbox is the runtime boundary where agent code executes. A compute driver creates it and connects two dedicated components: `openshell-sandbox` inside the workload boundary and `openshell-supervisor` outside it. +Kubernetes warm pooling is enabled by default, but only templates with startup +hints request spare workload/supervisor pairs. The driver caps total unassigned +inventory; operators can disable pooling through driver configuration or Helm. +Each claimed pair belongs to one logical sandbox and is never returned to the pool. +The gateway reserves each physical target in its database atomically with the +logical allocation intent before sending a claim to the driver. Concurrent +requests that lose the reservation immediately select another candidate without +retiring the winner's pair. Create and recovery use bounded retries for definite +claim rejection and persist each replacement before claiming it. Exhausted warm +capacity falls back to cold creation in the same invocation. Uncertain claims +retain their target and generation for idempotent recovery. +The Sandbox resource owns the assignment. Reconciliation copies its logical +sandbox name and ID onto both Pods as display labels and updates their sandbox-ID +annotations for SPIFFE identity. Physical pair selectors and registration +bindings remain stable. A successful claim wakes +registration waiters on the same gateway replica; polling durable state remains +the fallback for other replicas and recovery. Every wake revalidates identity. +Prepared pairs also carry template name and ID labels on their parent and both +Pods. These display labels survive assignment and do not affect pool compatibility. + ## Runtime Model Each sandbox has three trust levels: @@ -27,6 +47,10 @@ TCP Service, or VM vsock channel. Independent bidirectional `Exchange` RPCs carry lifecycle, exec, TCP, and forwarding traffic, while one persistent bidirectional `Mediate` RPC carries multiplexed DNS traffic. General application UDP is unsupported; UDP DNS remains mediated by the supervisor. +Boundary connection attempts bound transport establishment and TLS negotiation +to two seconds per attempt within a 30-second retry window. The window also +cancels an in-flight attempt at its deadline. Initial attachment can retry calls +for up to five minutes while the workload boots. The sandbox probes HTTP/2 connection liveness every five seconds and closes connections that miss a ten-second acknowledgement deadline. Closing a connection freezes the owned workload process tree and cancels its stream @@ -50,9 +74,13 @@ connections, including loopback aliases. Unix control listeners reject workload descendants using kernel peer credentials and process ancestry, while ordinary workload loopback and Unix services remain available. NetworkPolicy is an outer reachability fence, not a confidentiality boundary. -Each sandbox generation receives a fresh CA and distinct server/client leaves; -both endpoints bind the same workload identity and immutable driver resource -claims. Driver crates do not appear in generic process, network, SSH, or +Each sandbox generation receives a fresh CA and server certificate. The +supervisor verifies server TLS; the workload authenticates the supervisor using +a gateway-signed boundary JWT, not a TLS client certificate. Both endpoints bind +the workload identity and immutable driver resource claims. Kubernetes can +prepare the listener before logical assignment: the first JWT must also contain +the exact protected resource binding before the workload accepts its sandbox ID. +Driver crates do not appear in generic process, network, SSH, or session code. The supervisor exposes readiness only after the sandbox is confirmed and the @@ -656,6 +684,14 @@ A leader-owned scan runs independently of driver inventory. Expiry records `Error`/`ProvisioningTimedOut` before reclaiming compute; cleanup progress and backoff survive restart. Late runtime reports cannot replace that result. The record and restartable storage survive cleanup, including for ephemeral creates. +Allocation recovery checks the same persisted deadline before retrying compute. +Timeout cleanup retires an unclaimed warm candidate through the driver's claim +CAS before acknowledging reclamation; a candidate already claimed by the sandbox +uses normal stop semantics to retain storage. Cleanup clears allocation intent +only after retirement and reclamation succeed. Late allocation results are fenced +by provisioning attempt and runtime generation, and cannot rearm an expired +attempt. Records with leftover allocation intent require cleanup even if an older +gateway already recorded cleanup completion. Explicit start is blocked while cleanup is pending, then creates a fresh attempt using the latest configuration. Configuration edits alone never restart an expired sandbox. Legacy provisioning records receive one persisted rollout diff --git a/crates/openshell-cli/tests/ensure_providers_integration.rs b/crates/openshell-cli/tests/ensure_providers_integration.rs index 9541d38bf4..05cbc75876 100644 --- a/crates/openshell-cli/tests/ensure_providers_integration.rs +++ b/crates/openshell-cli/tests/ensure_providers_integration.rs @@ -677,6 +677,13 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("not implemented in test")) } + async fn register_supervisor( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("not implemented in test")) + } + async fn issue_sandbox_token( &self, _request: tonic::Request, diff --git a/crates/openshell-cli/tests/mtls_integration.rs b/crates/openshell-cli/tests/mtls_integration.rs index 59ba2d287d..6b412b0db3 100644 --- a/crates/openshell-cli/tests/mtls_integration.rs +++ b/crates/openshell-cli/tests/mtls_integration.rs @@ -529,6 +529,13 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("not implemented in test")) } + async fn register_supervisor( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("not implemented in test")) + } + async fn issue_sandbox_token( &self, _request: tonic::Request, diff --git a/crates/openshell-cli/tests/provider_commands_integration.rs b/crates/openshell-cli/tests/provider_commands_integration.rs index 916afcf247..bb169ef78c 100644 --- a/crates/openshell-cli/tests/provider_commands_integration.rs +++ b/crates/openshell-cli/tests/provider_commands_integration.rs @@ -1472,6 +1472,13 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("not implemented in test")) } + async fn register_supervisor( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("not implemented in test")) + } + async fn issue_sandbox_token( &self, _request: tonic::Request, diff --git a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs index 25bc9ca6e5..93c014a13b 100644 --- a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs +++ b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs @@ -988,6 +988,13 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("not implemented in test")) } + async fn register_supervisor( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("not implemented in test")) + } + async fn issue_sandbox_token( &self, _request: tonic::Request, diff --git a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs index 9e2412db97..ecf6a42fce 100644 --- a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs +++ b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs @@ -620,6 +620,13 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("not implemented in test")) } + async fn register_supervisor( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("not implemented in test")) + } + async fn issue_sandbox_token( &self, _request: tonic::Request, diff --git a/crates/openshell-core/src/grpc_client.rs b/crates/openshell-core/src/grpc_client.rs index 5304286fd2..8022de9b7b 100644 --- a/crates/openshell-core/src/grpc_client.rs +++ b/crates/openshell-core/src/grpc_client.rs @@ -274,6 +274,60 @@ async fn build_plain_channel(endpoint: &str) -> Result { .wrap_err("failed to connect to OpenShell server") } +/// Register with a driver-native projected credential, before installing any +/// operational token slots. Re-read the token on every reconnect for rotation. +pub async fn register_supervisor( + endpoint: &str, +) -> Result { + let path = std::env::var(sandbox_env::K8S_SA_TOKEN_FILE) + .into_diagnostic() + .wrap_err("registration requires a projected service-account token")?; + loop { + let token = tokio::fs::read_to_string(&path).await.into_diagnostic()?; + let result = match build_plain_channel(endpoint).await { + Ok(channel) => { + let mut client = OpenShellClient::new(channel); + let mut request = tonic::Request::new(crate::proto::RegisterSupervisorRequest {}); + request.metadata_mut().insert( + "authorization", + format!("Bearer {}", token.trim()) + .parse() + .into_diagnostic()?, + ); + request.set_timeout(Duration::from_secs(45)); + client + .register_supervisor(request) + .await + .map(tonic::Response::into_inner) + } + Err(_) => Err(Status::unavailable("registration transport unavailable")), + }; + match result { + Ok(assignment) => return Ok(assignment), + Err(error) + if matches!( + error.code(), + tonic::Code::Unavailable + | tonic::Code::DeadlineExceeded + | tonic::Code::Cancelled + | tonic::Code::Aborted + | tonic::Code::Unauthenticated + ) => + { + // Log only the code, never credential-bearing response bodies. + debug!(code = ?error.code(), "supervisor registration unavailable; retrying"); + tokio::time::sleep(Duration::from_secs(2)).await; + } + Err(error) => { + return Err(miette::miette!( + "supervisor registration rejected ({:?})", + error.code() + )); + } + } + } +} + /// Build a Bearer-authenticated channel to the gateway. /// /// First call per process resolves the sandbox JWT via the three-step diff --git a/crates/openshell-core/src/jwt.rs b/crates/openshell-core/src/jwt.rs index 5cd8c60438..ac96329301 100644 --- a/crates/openshell-core/src/jwt.rs +++ b/crates/openshell-core/src/jwt.rs @@ -174,6 +174,8 @@ mod session { runtime_generation: SandboxGenerationId, auth_epoch: CredentialEpoch, component: SessionComponent, + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + resource_binding: BTreeMap, } /// Durable identity shared by every short-lived token for one sandbox runtime. @@ -582,6 +584,32 @@ mod session { identity: &SandboxRuntimeIdentity, token_id: Uuid, issued_at: i64, + ) -> Result { + self.mint_bound(profile, identity, token_id, issued_at, BTreeMap::new()) + } + + /// Authorize initial assignment to an exact driver-provisioned runtime. + pub fn mint_bound_sandbox_token( + &self, + identity: &SandboxRuntimeIdentity, + resource_binding: BTreeMap, + ) -> Result { + self.mint_bound( + SessionTokenProfile::Sandbox, + identity, + Uuid::new_v4(), + self.clock.now_unix_seconds(), + resource_binding, + ) + } + + fn mint_bound( + &self, + profile: SessionTokenProfile, + identity: &SandboxRuntimeIdentity, + token_id: Uuid, + issued_at: i64, + resource_binding: BTreeMap, ) -> Result { let expires_at = issued_at.saturating_add( i64::try_from(self.ttl.as_secs()).map_err(|_| SessionJwtError::InvalidLifetime)?, @@ -597,6 +625,7 @@ mod session { runtime_generation: identity.runtime_generation.clone(), auth_epoch: identity.auth_epoch, component: SessionComponent::OpenShellSupervisor, + resource_binding, }; let mut header = Header::new(Algorithm::EdDSA); header.kid = Some(self.key_id.clone()); @@ -640,6 +669,8 @@ mod session { #[derive(Clone, Debug, PartialEq, Eq)] pub struct AuthenticatedSandboxSession { + /// Signed physical-runtime binding, required for initial late assignment. + pub resource_binding: BTreeMap, pub sandbox_id: SandboxId, pub runtime_generation: SandboxGenerationId, pub auth_epoch: CredentialEpoch, @@ -734,6 +765,7 @@ mod session { return Err(SessionJwtError::Expired); } Ok(AuthenticatedSandboxSession { + resource_binding: claims.resource_binding, sandbox_id: claims.sandbox_id, runtime_generation: claims.runtime_generation, auth_epoch: claims.auth_epoch, diff --git a/crates/openshell-driver-docker/src/lib.rs b/crates/openshell-driver-docker/src/lib.rs index 13d47bfe36..2b61dedc6a 100644 --- a/crates/openshell-driver-docker/src/lib.rs +++ b/crates/openshell-driver-docker/src/lib.rs @@ -2839,6 +2839,23 @@ fn validate_docker_outer_fence( } #[tonic::async_trait] impl ComputeDriver for ComputeDriverService { + async fn select_warm_pair( + &self, + request: Request, + ) -> Result, Status> { + let _ = &request; + Err(Status::unimplemented( + "warm pair allocation is not supported", + )) + } + + async fn sync_warm_pools( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("warm pools are not supported")) + } + type WatchSandboxesStream = WatchStream; async fn authenticate_sandbox( @@ -2992,6 +3009,23 @@ impl ComputeDriver for ComputeDriverService { #[tonic::async_trait] impl ComputeDriver for DockerComputeDriver { + async fn select_warm_pair( + &self, + request: Request, + ) -> Result, Status> { + let _ = &request; + Err(Status::unimplemented( + "warm pair allocation is not supported", + )) + } + + async fn sync_warm_pools( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("warm pools are not supported")) + } + async fn authenticate_sandbox( &self, _request: Request, diff --git a/crates/openshell-driver-docker/src/tests.rs b/crates/openshell-driver-docker/src/tests.rs index 4b0c9cef92..c9668ad577 100644 --- a/crates/openshell-driver-docker/src/tests.rs +++ b/crates/openshell-driver-docker/src/tests.rs @@ -638,7 +638,10 @@ async fn tracing_lifecycle_rpc_failures_export_docker_operation_spans() { async { ComputeDriver::create_sandbox( &driver, - Request::new(CreateSandboxRequest { sandbox: None }), + Request::new(CreateSandboxRequest { + sandbox: None, + ..Default::default() + }), ) .await .expect_err("missing sandbox should fail"); diff --git a/crates/openshell-driver-kubernetes/Cargo.toml b/crates/openshell-driver-kubernetes/Cargo.toml index 5b498a24d8..0aeaac01ef 100644 --- a/crates/openshell-driver-kubernetes/Cargo.toml +++ b/crates/openshell-driver-kubernetes/Cargo.toml @@ -33,6 +33,7 @@ kube-runtime = { workspace = true } k8s-openapi = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +sha2 = { workspace = true } clap = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } diff --git a/crates/openshell-driver-kubernetes/src/config.rs b/crates/openshell-driver-kubernetes/src/config.rs index f29ba79f5b..3640e35404 100644 --- a/crates/openshell-driver-kubernetes/src/config.rs +++ b/crates/openshell-driver-kubernetes/src/config.rs @@ -198,6 +198,7 @@ pub struct KubernetesComputeConfig { pub supervisor_image_pull_policy: Option, /// Cross-pod sandbox/supervisor settings. pub sandbox_runtime: KubernetesSandboxRuntimeConfig, + pub warm_pool: WarmPoolConfig, /// Corporate HTTP forward proxy used by the network supervisor for /// policy-approved TLS CONNECT egress. pub https_proxy: Option, @@ -312,6 +313,7 @@ impl Default for KubernetesComputeConfig { supervisor_image: config::default_supervisor_image(), supervisor_image_pull_policy: None, sandbox_runtime: KubernetesSandboxRuntimeConfig::default(), + warm_pool: WarmPoolConfig::default(), https_proxy: None, no_proxy: None, proxy_auth_secret_name: None, @@ -337,6 +339,7 @@ impl Default for KubernetesComputeConfig { impl KubernetesComputeConfig { /// Validate startup configuration without connecting to Kubernetes. pub fn validate_configuration(&self) -> Result<(), String> { + self.warm_pool.validate()?; self.validate_workspace_mode()?; self.validate_provider_spiffe_workload_api_socket_path()?; self.validate_sandbox_identity_config()?; @@ -1619,3 +1622,36 @@ mod tests { assert!(al.remove("ns1")); } } + +/// Limits for template-scoped, single-use spare pairs. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(default, deny_unknown_fields)] +pub struct WarmPoolConfig { + pub enabled: bool, + pub max_pairs: u32, + pub preparation_timeout_seconds: u64, + pub max_idle_seconds: u64, +} +impl Default for WarmPoolConfig { + fn default() -> Self { + Self { + enabled: true, + max_pairs: 10, + preparation_timeout_seconds: 300, + max_idle_seconds: 3600, + } + } +} + +impl WarmPoolConfig { + pub fn validate(&self) -> Result<(), String> { + if self.max_pairs > 1000 + || self.preparation_timeout_seconds < 30 + || self.max_idle_seconds < self.preparation_timeout_seconds + || self.max_idle_seconds > 86400 + { + return Err("warm_pool requires max_pairs <= 1000 and 30 <= preparation_timeout_seconds <= max_idle_seconds <= 86400".into()); + } + Ok(()) + } +} diff --git a/crates/openshell-driver-kubernetes/src/driver.rs b/crates/openshell-driver-kubernetes/src/driver.rs index 7e0c779074..0582090c9f 100644 --- a/crates/openshell-driver-kubernetes/src/driver.rs +++ b/crates/openshell-driver-kubernetes/src/driver.rs @@ -72,12 +72,15 @@ use serde::Deserialize; use std::collections::{BTreeMap, HashSet}; use std::path::{Path, PathBuf}; use std::pin::Pin; -use std::sync::Arc; +use std::sync::{Arc, atomic::AtomicUsize}; use std::time::{Duration, SystemTime}; use tokio::sync::{OnceCell, mpsc}; use tokio_stream::wrappers::ReceiverStream; use tracing::{debug, info, warn}; +#[path = "warm_pool.rs"] +mod warm_pool; + pub type WatchStream = Pin> + Send>>; @@ -165,7 +168,16 @@ impl KubernetesDriverError { KubeError::Api(api) if api.code == 409 && api.reason == "AlreadyExists" => { Self::AlreadyExists } - KubeError::Api(api) if api.code == 404 => Self::NotFound, + KubeError::Api(api) if api.code == 404 => { + // Preserve the missing resource's identity before mapping to + // the driver contract's generic sandbox-not-found response. + warn!( + reason = %api.reason, + message = %api.message, + "Kubernetes API resource not found" + ); + Self::NotFound + } other => Self::Message(other.to_string()), } } @@ -639,6 +651,8 @@ pub struct KubernetesComputeDriver { sandbox_api_version: Arc>, config: KubernetesComputeConfig, operator_allowlist: Option, + pool_targets: Arc>>, + pool_reconcile_cursor: Arc, } impl std::fmt::Debug for KubernetesComputeDriver { @@ -666,6 +680,8 @@ impl KubernetesComputeDriver { sandbox_api_version: Arc::new(OnceCell::new()), config, operator_allowlist: None, + pool_targets: Arc::default(), + pool_reconcile_cursor: Arc::default(), } } @@ -673,6 +689,10 @@ impl KubernetesComputeDriver { config: KubernetesComputeConfig, shutdown_rx: tokio::sync::watch::Receiver, ) -> Result { + config + .warm_pool + .validate() + .map_err(KubernetesDriverError::Precondition)?; config .validate_workspace_mode() .map_err(KubernetesDriverError::Precondition)?; @@ -736,7 +756,10 @@ impl KubernetesComputeDriver { sandbox_api_version: Arc::new(OnceCell::new()), config, operator_allowlist, + pool_targets: Arc::default(), + pool_reconcile_cursor: Arc::default(), }; + driver.spawn_pool_controller(shutdown_rx); if driver.workspace_mode() == WorkspaceMode::Shared { driver.backfill_gateway_id_labels().await?; @@ -778,6 +801,23 @@ impl KubernetesComputeDriver { /// Authenticate the projected `ServiceAccount` token used by a sandbox pod. pub async fn authenticate_sandbox(&self, credential: &str) -> Result { + Ok(self.authenticate_proxy(credential, false).await?.sandbox_id) + } + + pub async fn authenticate_supervisor( + &self, + credential: &str, + ) -> Result + { + self.authenticate_proxy(credential, true).await + } + + async fn authenticate_proxy( + &self, + credential: &str, + registration: bool, + ) -> Result + { let reviews: Api = Api::all(self.client.clone()); let review = TokenReview { metadata: ObjectMeta::default(), @@ -792,7 +832,7 @@ impl KubernetesComputeDriver { .await .map_err(|error| { warn!(%error, "Kubernetes TokenReview failed"); - tonic::Status::internal("Kubernetes TokenReview failed") + tonic::Status::unavailable("Kubernetes TokenReview failed") })?; let status = review .status @@ -811,12 +851,15 @@ impl KubernetesComputeDriver { .await .map_err(|error| { warn!(pod = %identity.pod_name, %error, "failed to read authenticated sandbox pod"); - tonic::Status::internal("failed to read authenticated sandbox pod") + tonic::Status::unavailable("failed to read authenticated sandbox pod") })? .ok_or_else(|| { tonic::Status::permission_denied("authenticated sandbox pod not found") })?; validate_pod_uid(&pod, &identity.pod_uid)?; + if registration { + return self.registration_for_pod(&pod, &identity.namespace).await; + } let sandbox_id = pod_sandbox_id(&pod)?; let (owner, via_proxy_control) = Self::resolve_sandbox_owner(&pod, &sandbox_id)?; let sandboxes = self @@ -831,7 +874,129 @@ impl KubernetesComputeDriver { })?.ok_or_else(|| tonic::Status::permission_denied("sandbox owner not found"))?; validate_sandbox_owner_identity(&owner, &sandbox_id, &sandbox)?; require_proxy_control_authentication(via_proxy_control)?; - Ok(sandbox_id) + Ok( + openshell_core::proto::compute::v1::AuthenticateSandboxResponse { + sandbox_id, + registration: None, + }, + ) + } + + async fn registration_for_pod( + &self, + pod: &Pod, + namespace: &str, + ) -> Result + { + use openshell_core::proto::compute::v1::{ + AuthenticateSandboxResponse, SupervisorRegistration, + }; + let denied = + || tonic::Status::permission_denied("proxy is not part of the prepared runtime"); + let labels = pod.metadata.labels.as_ref().ok_or_else(denied)?; + if pod.metadata.deletion_timestamp.is_some() + || labels.get(BOUNDARY_ROLE_LABEL).map(String::as_str) != Some("supervisor") + || labels.get(LABEL_GATEWAY_ID) != Some(&self.config.gateway_id) + { + return Err(denied()); + } + let owner = sandbox_owner_reference(pod, false)?; + let api = self + .supported_agent_sandbox_api(self.client.clone(), namespace) + .await + .map_err(|_| tonic::Status::unavailable("Sandbox API unavailable"))?; + let sandbox = api + .api + .get(&owner.name) + .await + .map_err(|_| tonic::Status::unavailable("Sandbox resource unavailable"))?; + if sandbox.metadata.uid.as_deref() != Some(&owner.uid) + || sandbox.metadata.deletion_timestamp.is_some() + || !is_openshell_managed(&sandbox) + || annotation_or_label(&sandbox, LABEL_GATEWAY_ID).as_deref() + != Some(&self.config.gateway_id) + { + return Err(denied()); + } + let spec = pod.spec.as_ref().ok_or_else(denied)?; + let secret_name = spec + .volumes + .as_deref() + .unwrap_or_default() + .iter() + .find(|volume| volume.name == "bootstrap") + .and_then(|volume| volume.secret.as_ref()) + .and_then(|secret| secret.secret_name.as_ref()) + .ok_or_else(denied)?; + let secret = Api::::namespaced(self.client.clone(), namespace) + .get(secret_name) + .await + .map_err(|error| { + warn!(%namespace, %secret_name, %error, "failed to read proxy registration bootstrap Secret"); + tonic::Status::unavailable("proxy preparation is not available") + })?; + let pod_uid = pod.metadata.uid.as_ref().ok_or_else(denied)?; + if !secret + .metadata + .owner_references + .as_deref() + .unwrap_or_default() + .iter() + .any(|owner| owner.kind == "Pod" && owner.api_version == "v1" && &owner.uid == pod_uid) + { + return Err(denied()); + } + let bytes = secret + .data + .as_ref() + .and_then(|data| data.get(crate::sandbox_runtime::BACKEND_DESCRIPTOR_KEY)) + .ok_or_else(denied)? + .0 + .clone(); + let mut descriptor: openshell_sandbox_backend::boundary_protocol::SandboxRuntimeDescriptor = + serde_json::from_slice(&bytes).map_err(|_| denied())?; + let claims = &descriptor.resource_claims; + if claims.get("kubernetes.sandbox_resource_uid") != Some(&owner.uid) + || claims.get("kubernetes.supervisor_pod_uid") != Some(pod_uid) + { + return Err(denied()); + } + let workload_uid = claims + .get("kubernetes.workload_pod_uid") + .ok_or_else(denied)?; + let pods = Api::::namespaced(self.client.clone(), namespace) + .list(&ListParams::default().labels(&format!("{BOUNDARY_ROLE_LABEL}=workload"))) + .await + .map_err(|_| tonic::Status::unavailable("workload inventory unavailable"))?; + if !pods.items.iter().any(|workload| { + workload.metadata.uid.as_ref() == Some(workload_uid) + && workload.metadata.deletion_timestamp.is_none() + && sandbox_owner_reference(workload, true) + .is_ok_and(|actual| actual.uid == owner.uid) + }) { + return Err(denied()); + } + Box::pin(self.record_pool_registration(&api.api, &sandbox)).await?; + let sandbox_id = annotation_or_label(&sandbox, LABEL_SANDBOX_ID).unwrap_or_default(); + let workload_pod_uid = workload_uid.clone(); + descriptor.boundary_id.clone_from(&sandbox_id); + Ok(AuthenticateSandboxResponse { + sandbox_id, + registration: Some(SupervisorRegistration { + instance_id: pod_uid.clone(), + sandbox_resource_uid: owner.uid.clone(), + workload_pod_uid, + backend_descriptor: serde_json::to_vec(&descriptor).map_err(|_| denied())?, + main_process_spec: annotation_or_label( + &sandbox, + ANNOTATION_SANDBOX_RUNTIME_MAIN_PROCESS_SPEC, + ) + .unwrap_or_default(), + runtime_generation: descriptor.generation, + session_id: descriptor.session_id.to_string(), + resource_binding: descriptor.resource_claims.into_iter().collect(), + }), + }) } #[allow(clippy::result_large_err)] @@ -1598,6 +1763,9 @@ impl KubernetesComputeDriver { Ok(Ok(list)) => { let mut sandboxes = Vec::new(); for obj in list.items { + if warm_pool::is_pool_pair(&obj) { + continue; + } let name = obj.metadata.name.clone().unwrap_or_default(); let ns = obj .metadata @@ -1653,12 +1821,16 @@ impl KubernetesComputeDriver { )] pub async fn create_sandbox(&self, sandbox: &Sandbox) -> Result<(), KubernetesDriverError> { let span_status = openshell_otel::ErrorStatusGuard::current(); - let result = self.create_sandbox_inner(sandbox).await; + let result = Box::pin(self.create_sandbox_inner(sandbox, None)).await; span_status.finish(result) } #[allow(clippy::similar_names)] - async fn create_sandbox_inner(&self, sandbox: &Sandbox) -> Result<(), KubernetesDriverError> { + async fn create_sandbox_inner( + &self, + sandbox: &Sandbox, + pool: Option<&warm_pool::Preparation>, + ) -> Result<(), KubernetesDriverError> { let gpu_requirements = sandbox .spec .as_ref() @@ -1717,7 +1889,13 @@ impl KubernetesComputeDriver { .resolve_sandbox_identity_in_namespace(&target_namespace) .await; - let generation = random_sandbox_runtime_token(); + let authentication = match pool { + Some(pool) => pool.authentication.clone(), + None => warm_pool::PreparationAuthentication::from_sandbox(sandbox)?, + }; + // Allocation reads this generation from the Sandbox metadata; it must + // match the generation presented by the prepared proxy at registration. + let generation = authentication.runtime_generation.to_string(); let proxy_names = SandboxRuntimeNames::for_generation(&sandbox.id, &generation); let main_process_spec = openshell_core::sandbox_env::MainProcessConfig::encode_driver_spec( sandbox.spec.as_ref(), @@ -1743,7 +1921,11 @@ impl KubernetesComputeDriver { boundary_port: self.config.sandbox_runtime.boundary_port, sandbox_secret_name: &proxy_names.sandbox_secret, }; - let kube_name = self.config.kube_resource_name(workspace, name); + let kube_name = if pool.is_some() { + name.to_string() + } else { + self.config.kube_resource_name(workspace, name) + }; let mut data = sandbox_to_k8s_spec(sandbox.spec.as_ref(), ¶ms) .map_err(KubernetesDriverError::InvalidArgument)?; self.create_sandbox_runtime_fence(&target_namespace, &proxy_names) @@ -1803,6 +1985,9 @@ impl KubernetesComputeDriver { ..Default::default() }; + if let Some(pool) = pool { + pool.decorate(&mut obj, &authentication); + } obj.data = data; let created = match tokio::time::timeout( KUBE_API_TIMEOUT, @@ -1840,27 +2025,29 @@ impl KubernetesComputeDriver { ))); } }; - if let Err(error) = self - .create_sandbox_runtime_companions( - sandbox, - &target_namespace, - &kube_name, - &agent_sandbox_api, - &created, - &proxy_names, - &generation, - resolved_user_id, - resolved_group_id, - &main_process_spec, - &log_level, - ) - .await + if let Err(error) = Box::pin(self.create_sandbox_runtime_companions( + sandbox, + &target_namespace, + &kube_name, + &agent_sandbox_api, + &created, + &proxy_names, + &generation, + resolved_user_id, + resolved_group_id, + &main_process_spec, + &log_level, + &authentication, + )) + .await { warn!(sandbox_id = %sandbox.id, %error, "sandbox-runtime provisioning failed; rolling back Sandbox CR"); - let _ = agent_sandbox_api - .api - .delete(&kube_name, &DeleteParams::default()) - .await; + if pool.is_none() { + let _ = agent_sandbox_api + .api + .delete(&kube_name, &DeleteParams::default()) + .await; + } return Err(error); } Ok(()) @@ -1937,6 +2124,7 @@ impl KubernetesComputeDriver { uid: u32, gid: u32, sandbox_secret_name: &str, + prepared_workload_uid: Option<&str>, ) -> Result<(), KubernetesDriverError> { let fail = |message: &str| { KubernetesDriverError::Precondition(format!( @@ -2008,6 +2196,7 @@ impl KubernetesComputeDriver { .unwrap_or_default() .iter() .any(|gate| gate.name == SANDBOX_BOOTSTRAP_SCHEDULING_GATE) + && prepared_workload_uid.is_none_or(|uid| pod.metadata.uid.as_deref() != Some(uid)) { return Err(fail("bootstrap scheduling gate missing")); } @@ -2150,6 +2339,7 @@ impl KubernetesComputeDriver { agent_gid: u32, main_process_spec: &str, log_level: &str, + launch_authentication: &warm_pool::PreparationAuthentication, ) -> Result<(), KubernetesDriverError> { let cr_uid = sandbox_cr.metadata.uid.as_deref().ok_or_else(|| { KubernetesDriverError::Message("created Sandbox CR has no UID".to_string()) @@ -2170,19 +2360,28 @@ impl KubernetesComputeDriver { false, ); let services: Api = Api::namespaced(self.client.clone(), namespace); - let service = services - .create( - &PostParams::default(), - &boundary_service( - namespace, - names, - &sandbox.id, - self.config.sandbox_runtime.boundary_port, - dependent_owner.clone(), - ), - ) - .await - .map_err(KubernetesDriverError::from_kube)?; + let service = warm_pool::create_owned( + &services, + &boundary_service( + namespace, + names, + &sandbox.id, + self.config.sandbox_runtime.boundary_port, + dependent_owner.clone(), + ), + ) + .await?; + let service_uid = + service.metadata.uid.clone().ok_or_else(|| { + KubernetesDriverError::Message("boundary Service has no UID".into()) + })?; + if annotation_or_label(sandbox_cr, warm_pool::SERVICE_UID) + .is_some_and(|expected| expected != service_uid) + { + return Err(KubernetesDriverError::Precondition( + "boundary Service incarnation changed".into(), + )); + } let service_ip: std::net::IpAddr = service .spec .and_then(|spec| spec.cluster_ip) @@ -2198,45 +2397,43 @@ impl KubernetesComputeDriver { })?; let pods: Api = Api::namespaced(self.client.clone(), namespace); - let supervisor = pods - .create( - &PostParams::default(), - &supervisor_pod( - namespace, - names, - &sandbox.id, - &sandbox.name, - &self.config.gateway_id, - &self.config.supervisor_image, - self.config.supervisor_image_pull_policy, - &self.config.service_account_name, - agent_uid, - agent_gid, - &self.config.image_pull_secrets, - &self.config.grpc_endpoint, - &self.config.client_tls_secret_name, - main_process_spec, - log_level, - self.config.effective_sa_token_ttl_secs(), - self.config.https_proxy.as_deref(), - self.config.no_proxy.as_deref(), + let supervisor = warm_pool::create_owned( + &pods, + &supervisor_pod( + namespace, + names, + &sandbox.id, + &sandbox.name, + &self.config.gateway_id, + &self.config.supervisor_image, + self.config.supervisor_image_pull_policy, + &self.config.service_account_name, + agent_uid, + agent_gid, + &self.config.image_pull_secrets, + &self.config.grpc_endpoint, + &self.config.client_tls_secret_name, + main_process_spec, + log_level, + self.config.effective_sa_token_ttl_secs(), + self.config.https_proxy.as_deref(), + self.config.no_proxy.as_deref(), + self.config + .proxy_auth_secret_name + .as_deref() + .zip(self.config.proxy_auth_secret_key.as_deref()), + self.config.proxy_auth_allow_insecure == Some(true), + self.config.proxy_connect_by_hostname == Some(true), + self.config.provider_spiffe_enabled().then_some( self.config - .proxy_auth_secret_name - .as_deref() - .zip(self.config.proxy_auth_secret_key.as_deref()), - self.config.proxy_auth_allow_insecure == Some(true), - self.config.proxy_connect_by_hostname == Some(true), - self.config.provider_spiffe_enabled().then_some( - self.config - .provider_spiffe_workload_api_socket_path - .as_str(), - ), - dependent_owner.clone(), - ) - .map_err(KubernetesDriverError::Message)?, + .provider_spiffe_workload_api_socket_path + .as_str(), + ), + dependent_owner.clone(), ) - .await - .map_err(KubernetesDriverError::from_kube)?; + .map_err(KubernetesDriverError::Message)?, + ) + .await?; let supervisor_uid = supervisor.metadata.uid.ok_or_else(|| { KubernetesDriverError::Message("supervisor Pod has no UID".to_string()) })?; @@ -2267,6 +2464,29 @@ impl KubernetesComputeDriver { let workload_pod = self .wait_for_bootstrap_workload_pod(&pods, cr_name, cr_uid) .await?; + // A retry may observe a released workload while the final Released + // annotation is still missing. Only the exact persisted preparation + // may bypass the initial scheduling-gate requirement. + let prepared_workload_uid = + annotation_or_label(sandbox_cr, ANNOTATION_SANDBOX_RUNTIME_WORKLOAD_UID); + let prepared_pair_matches = warm_pool::is_pool_pair(sandbox_cr) + && [ + (warm_pool::SERVICE_UID, service_uid.as_str()), + ( + ANNOTATION_SANDBOX_RUNTIME_SUPERVISOR_UID, + supervisor_uid.as_str(), + ), + ( + ANNOTATION_SANDBOX_RUNTIME_NETWORK_POLICY_UID, + fence_uid.as_str(), + ), + ( + ANNOTATION_SANDBOX_RUNTIME_NETWORK_POLICY_VERSION, + fence_resource_version.as_str(), + ), + ] + .iter() + .all(|(key, value)| annotation_or_label(sandbox_cr, key).as_deref() == Some(*value)); Self::validate_capability_free_workload_pod( &workload_pod, cr_uid, @@ -2274,7 +2494,19 @@ impl KubernetesComputeDriver { agent_uid, agent_gid, &names.sandbox_secret, + prepared_workload_uid + .as_deref() + .filter(|_| prepared_pair_matches), )?; + let workload_released = !workload_pod + .spec + .as_ref() + .expect("validated Pod spec") + .scheduling_gates + .as_deref() + .unwrap_or_default() + .iter() + .any(|gate| gate.name == SANDBOX_BOOTSTRAP_SCHEDULING_GATE); let workload_pod_uid = workload_pod.metadata.uid.clone().ok_or_else(|| { KubernetesDriverError::Message("workload Pod has no UID".to_string()) @@ -2287,6 +2519,7 @@ impl KubernetesComputeDriver { "metadata": { "resourceVersion": version, "annotations": { + warm_pool::SERVICE_UID: service_uid.clone(), ANNOTATION_SANDBOX_RUNTIME_WORKLOAD_UID: workload_pod_uid.clone(), ANNOTATION_SANDBOX_RUNTIME_SUPERVISOR_UID: supervisor_uid.clone(), ANNOTATION_SANDBOX_RUNTIME_NETWORK_POLICY_UID: fence_uid.clone(), @@ -2297,16 +2530,6 @@ impl KubernetesComputeDriver { }) .await?; - let launch_authentication = sandbox - .spec - .as_ref() - .filter(|spec| !spec.launch_authentication.is_empty()) - .ok_or_else(|| { - KubernetesDriverError::Precondition( - "Kubernetes sandbox launch authentication is required".to_string(), - ) - }) - .and_then(|spec| decode_launch_authentication(&spec.launch_authentication))?; let mut child_env = sandbox .spec .as_ref() @@ -2319,7 +2542,7 @@ impl KubernetesComputeDriver { } child_env.retain(|name, _| !name.starts_with("OPENSHELL_")); let host_gateway_ip = self.config.host_gateway_ip.parse().ok(); - let session_id = launch_authentication.supervisor.session_id; + let session_id = launch_authentication.session_id; let tls = generate_sandbox_tls_material(session_id) .map_err(|error| KubernetesDriverError::Message(error.to_string()))?; let verification_keys = @@ -2334,15 +2557,12 @@ impl KubernetesComputeDriver { ) .map_err(|error| KubernetesDriverError::Message(error.to_string()))?; let provisioned = KubernetesSandboxRuntimeBoundarySpec { - boundary_id: sandbox.id.clone(), - generation: launch_authentication - .supervisor - .runtime_generation - .to_string(), + boundary_id: String::new(), + generation: launch_authentication.runtime_generation.to_string(), session_id, - session_rotation: launch_authentication.supervisor.session_rotation, - auth_epoch: launch_authentication.supervisor.auth_epoch, - gateway_id: launch_authentication.gateway_id, + session_rotation: launch_authentication.session_rotation, + auth_epoch: launch_authentication.auth_epoch, + gateway_id: launch_authentication.gateway_id.clone(), verification_keys, namespace_uid, sandbox_resource_uid: cr_uid.to_string(), @@ -2409,9 +2629,6 @@ impl KubernetesComputeDriver { names, &sandbox.id, descriptor.payload, - serde_json::to_vec(&launch_authentication.supervisor).map_err(|error| { - KubernetesDriverError::Message(format!("encode supervisor auth bundle: {error}")) - })?, proxy_ca.certificate_pem.into_bytes(), proxy_ca.private_key_pem.into_bytes(), OwnerReference { @@ -2424,14 +2641,85 @@ impl KubernetesComputeDriver { }, ); let secrets = Api::::namespaced(self.client.clone(), namespace); - secrets - .create(&PostParams::default(), &sandbox_secret) - .await - .map_err(KubernetesDriverError::from_kube)?; - secrets - .create(&PostParams::default(), &supervisor_secret) - .await - .map_err(KubernetesDriverError::from_kube)?; + // One immutable parent-owned journal commits both halves before either + // bootstrap Secret is published. Retries preserve the original TLS keys. + let journal = Secret { + metadata: ObjectMeta { + name: Some(format!("{}-pair", names.sandbox_secret)), + labels: Some(BTreeMap::from([ + (LABEL_SANDBOX_ID.to_string(), sandbox.id.clone()), + ("openshell.ai/component".into(), "pair-bootstrap".into()), + ])), + namespace: Some(namespace.to_string()), + owner_references: Some(vec![dependent_owner]), + ..Default::default() + }, + immutable: Some(true), + data: Some(BTreeMap::from([ + ( + "workload".into(), + k8s_openapi::ByteString( + serde_json::to_vec(&sandbox_secret) + .map_err(|e| KubernetesDriverError::Message(e.to_string()))?, + ), + ), + ( + "proxy".into(), + k8s_openapi::ByteString( + serde_json::to_vec(&supervisor_secret) + .map_err(|e| KubernetesDriverError::Message(e.to_string()))?, + ), + ), + ])), + ..Default::default() + }; + let journal = if workload_released { + // Released listeners already use the journal's TLS material. Never + // replace it with fresh keys if the journal disappeared. + let existing = secrets + .get(journal.metadata.name.as_deref().expect("journal name")) + .await + .map_err(KubernetesDriverError::from_kube)?; + if existing.metadata.deletion_timestamp.is_some() + || existing.metadata.owner_references != journal.metadata.owner_references + { + return Err(KubernetesDriverError::Precondition( + "released pair journal has conflicting ownership".into(), + )); + } + existing + } else { + warm_pool::create_owned(&secrets, &journal).await? + }; + for key in ["workload", "proxy"] { + let data = journal + .data + .as_ref() + .and_then(|data| data.get(key)) + .ok_or_else(|| { + KubernetesDriverError::Precondition( + "incomplete pair preparation journal".into(), + ) + })?; + let secret: Secret = serde_json::from_slice(&data.0) + .map_err(|e| KubernetesDriverError::Message(e.to_string()))?; + let expected_owner = if key == "workload" { + &sandbox_secret.metadata.owner_references + } else { + &supervisor_secret.metadata.owner_references + }; + if &secret.metadata.owner_references != expected_owner { + return Err(KubernetesDriverError::Precondition( + "prepared Pod incarnation changed".into(), + )); + } + let actual = warm_pool::create_owned(&secrets, &secret).await?; + if actual.data != secret.data { + return Err(KubernetesDriverError::Precondition( + "bootstrap Secret changed".into(), + )); + } + } pods.patch( &names.supervisor_pod, @@ -2451,13 +2739,15 @@ impl KubernetesComputeDriver { sandbox_runtime_bootstrap_phase_patch(version, SandboxRuntimeBootstrapPhase::Released) }) .await?; - spawn_sandbox_runtime_bootstrap_completion( - pods.clone(), - sandbox_api.api.clone(), - names.supervisor_pod.clone(), - cr_name.to_string(), - Some(cr_uid.to_string()), - ); + if !warm_pool::is_pool_pair(sandbox_cr) { + spawn_sandbox_runtime_bootstrap_completion( + pods.clone(), + sandbox_api.api.clone(), + names.supervisor_pod.clone(), + cr_name.to_string(), + Some(cr_uid.to_string()), + ); + } // Return while the CR remains explicitly bootstrapping. The gateway // can now commit the sandbox configuration required by a policy-less // control process without deadlocking behind this driver call. Only @@ -2534,6 +2824,7 @@ impl KubernetesComputeDriver { agent_uid, agent_gid, &names.sandbox_secret, + None, )?; let workload_pod_uid = workload_pod.metadata.uid.clone().ok_or_else(|| { @@ -2572,7 +2863,7 @@ impl KubernetesComputeDriver { ) .map_err(|error| KubernetesDriverError::Message(error.to_string()))?; let provisioned = KubernetesSandboxRuntimeBoundarySpec { - boundary_id: sandbox_id.to_string(), + boundary_id: String::new(), generation: launch_authentication .supervisor .runtime_generation @@ -2647,9 +2938,6 @@ impl KubernetesComputeDriver { names, sandbox_id, descriptor.payload, - serde_json::to_vec(&launch_authentication.supervisor).map_err(|error| { - KubernetesDriverError::Message(format!("encode supervisor auth bundle: {error}")) - })?, proxy_ca.certificate_pem.into_bytes(), proxy_ca.private_key_pem.into_bytes(), OwnerReference { @@ -2715,10 +3003,10 @@ impl KubernetesComputeDriver { } async fn stop_sandbox_inner(&self, sandbox_id: &str) -> Result<(), KubernetesDriverError> { - let (agent_sandbox_api, kube_name, pod_name, namespace, stop_timeout, phase) = + let (agent_sandbox_api, kube_name, pod_name, namespace, stop_timeout, phase, resource_id) = self.prepare_sandbox_stop(sandbox_id).await?; if phase != Some(SandboxRuntimeBootstrapPhase::Suspending) { - self.delete_sandbox_runtime_supervisor_pod(sandbox_id, &namespace) + self.delete_sandbox_runtime_supervisor_pod(&resource_id, &namespace) .await?; patch_dynamic_object_with_resource_version_retry( &agent_sandbox_api.api, @@ -2766,8 +3054,11 @@ impl KubernetesComputeDriver { pod_is_gone, ); if stop_is_complete { - self.delete_sandbox_runtime_supervisor(sandbox_id, &namespace) - .await?; + self.delete_sandbox_runtime_supervisor( + &warm_pool::resource_id(&object), + &namespace, + ) + .await?; patch_dynamic_object_with_resource_version_retry( &agent_sandbox_api.api, &kube_name, @@ -2856,7 +3147,12 @@ impl KubernetesComputeDriver { .namespace .as_deref() .unwrap_or(&self.config.namespace); - if sandbox_runtime_control_availability(&self.client, namespace, sandbox_id).await + if sandbox_runtime_control_availability( + &self.client, + namespace, + &warm_pool::resource_id(&object), + ) + .await == SandboxRuntimeControlAvailability::Available && sandbox_runtime_bootstrap_is_ready_to_complete(&object) { @@ -2919,10 +3215,11 @@ impl KubernetesComputeDriver { )); } - let names = SandboxRuntimeNames::for_generation(sandbox_id, generation.as_str()); + let resource_id = warm_pool::resource_id(&object); + let names = SandboxRuntimeNames::for_generation(&resource_id, generation.as_str()); self.create_sandbox_runtime_fence(&namespace, &names) .await?; - self.delete_sandbox_runtime_supervisor(sandbox_id, &namespace) + self.delete_sandbox_runtime_supervisor(&warm_pool::resource_id(&object), &namespace) .await?; let main_process_spec = required_sandbox_annotation(&object, ANNOTATION_SANDBOX_RUNTIME_MAIN_PROCESS_SPEC)?; @@ -2940,7 +3237,7 @@ impl KubernetesComputeDriver { &supervisor_pod( &namespace, &names, - sandbox_id, + &resource_id, &sandbox_name, &self.config.gateway_id, &self.config.supervisor_image, @@ -3033,7 +3330,7 @@ impl KubernetesComputeDriver { &namespace, cr_name, &sandbox_api, - sandbox_id, + &resource_id, cr_uid, &names, generation.as_str(), @@ -3069,6 +3366,7 @@ impl KubernetesComputeDriver { String, Duration, Option, + String, ), KubernetesDriverError, > { @@ -3144,6 +3442,7 @@ impl KubernetesComputeDriver { namespace, stop_timeout, phase, + warm_pool::resource_id(&object), )) } @@ -3213,7 +3512,11 @@ impl KubernetesComputeDriver { namespace: &str, ) -> Result<(), KubernetesDriverError> { let secrets = Api::::namespaced(self.client.clone(), namespace); - for component in [SANDBOX_SECRET_COMPONENT, SUPERVISOR_SECRET_COMPONENT] { + for component in [ + SANDBOX_SECRET_COMPONENT, + SUPERVISOR_SECRET_COMPONENT, + "pair-bootstrap", + ] { let selector = format!("openshell.ai/sandbox-id={sandbox_id},openshell.ai/component={component}"); let items = secrets @@ -3485,7 +3788,8 @@ impl KubernetesComputeDriver { .await; continue; } - let names = SandboxRuntimeNames::new(&sandbox_id); + let names = SandboxRuntimeNames::new(&warm_pool::resource_id(&object)); + self.reconcile_pair_metadata(&object).await; let policies = Api::::namespaced(self.client.clone(), namespace); match self.create_sandbox_runtime_fence(namespace, &names).await { Ok(()) => {} @@ -3515,7 +3819,12 @@ impl KubernetesComputeDriver { continue; } if sandbox_runtime_bootstrap_in_progress(&object) { - if sandbox_runtime_control_availability(&self.client, namespace, &sandbox_id).await + if sandbox_runtime_control_availability( + &self.client, + namespace, + &warm_pool::resource_id(&object), + ) + .await == SandboxRuntimeControlAvailability::Available && sandbox_runtime_bootstrap_is_ready_to_complete(&object) { @@ -3577,7 +3886,11 @@ impl KubernetesComputeDriver { } } match self - .reconcile_sandbox_runtime_supervisor(&sandbox_id, namespace, desired_running) + .reconcile_sandbox_runtime_supervisor( + &warm_pool::resource_id(&object), + namespace, + desired_running, + ) .await { Ok(()) => {} @@ -3590,8 +3903,12 @@ impl KubernetesComputeDriver { warn!(sandbox_id, %error, "failed to reconcile sandbox-runtime supervisor Pod"); } } - let availability = - sandbox_runtime_control_availability(&self.client, namespace, &sandbox_id).await; + let availability = sandbox_runtime_control_availability( + &self.client, + namespace, + &warm_pool::resource_id(&object), + ) + .await; if desired_running && availability == SandboxRuntimeControlAvailability::Unavailable { warn!( sandbox_id, @@ -3631,7 +3948,7 @@ impl KubernetesComputeDriver { } } if let Err(error) = self - .delete_sandbox_runtime_supervisor(sandbox_id, namespace) + .delete_sandbox_runtime_supervisor(&warm_pool::resource_id(object), namespace) .await { warn!(sandbox_id, %error, "could not finish sandbox-runtime suspension cleanup"); @@ -3672,7 +3989,7 @@ impl KubernetesComputeDriver { cr_name: &str, ) { if let Err(error) = self - .delete_sandbox_runtime_supervisor_pod(sandbox_id, namespace) + .delete_sandbox_runtime_supervisor_pod(&warm_pool::resource_id(object), namespace) .await { warn!(sandbox_id, %error, "could not release sandbox-runtime control; reconciliation will retry"); @@ -4588,6 +4905,9 @@ fn sandbox_annotations(sandbox: &Sandbox) -> BTreeMap { } fn sandbox_id_from_object(obj: &DynamicObject) -> Result { + if warm_pool::is_pool_pair(obj) { + return Err("unassigned pool pair".to_string()); + } if let Some(annotations) = obj.metadata.annotations.as_ref() && let Some(id) = annotations.get(LABEL_SANDBOX_ID) { @@ -5024,7 +5344,7 @@ async fn sandbox_runtime_workload_generation_matches( async fn sandbox_runtime_supervisor_generation_matches( client: &Client, namespace: &str, - sandbox_id: &str, + _sandbox_id: &str, sandbox: &DynamicObject, ) -> SandboxRuntimeControlAvailability { let Some(expected_uid) = sandbox @@ -5035,7 +5355,7 @@ async fn sandbox_runtime_supervisor_generation_matches( else { return SandboxRuntimeControlAvailability::Unavailable; }; - let names = SandboxRuntimeNames::new(sandbox_id); + let names = SandboxRuntimeNames::new(&warm_pool::resource_id(sandbox)); let pods = Api::::namespaced(client.clone(), namespace); match tokio::time::timeout(KUBE_API_TIMEOUT, pods.get_opt(&names.supervisor_pod)).await { Ok(Ok(Some(pod))) if pod.metadata.uid.as_deref() == Some(expected_uid.as_str()) => { @@ -5073,10 +5393,11 @@ async fn sandbox_from_object_with_sandbox_runtime_readiness( mark_sandbox_runtime_bootstrapping(&mut sandbox); } if !sandbox_id.is_empty() { + let resource_id = warm_pool::resource_id(&obj); let dependencies = Box::pin(sandbox_runtime_control_availability( client, &object_namespace, - &sandbox_id, + &resource_id, )); let workload_generation = Box::pin(sandbox_runtime_workload_generation_matches( client, @@ -5990,6 +6311,10 @@ fn sandbox_template_to_k8s_with_validated_config( let mut container = serde_json::Map::new(); container.insert("name".to_string(), serde_json::json!("agent")); + container.insert("readinessProbe".to_string(), serde_json::json!({ + "exec": {"command": [format!("{SANDBOX_RUNTIME_MOUNT_PATH}/openshell-sandbox"), "boundary-health"]}, + "periodSeconds": 2, "timeoutSeconds": 1, "failureThreshold": 3 + })); // Use template image if provided, otherwise fall back to default let image = if template.image.is_empty() { params.default_image @@ -7610,6 +7935,8 @@ mod tests { "namespace": "openshell", "resourceVersion": "42", "annotations": { + LABEL_SANDBOX_ID: "logical-sandbox", + "openshell.ai/warm-pair-id": "sandbox-1", SANDBOX_POD_NAME_ANNOTATION: "workload-pod", ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_PHASE: SandboxRuntimeBootstrapPhase::Suspending.as_str() @@ -7684,6 +8011,11 @@ mod tests { "/api/v1/namespaces/openshell/secrets", no_secrets(), ), + ( + http::Method::GET, + "/api/v1/namespaces/openshell/secrets", + no_secrets(), + ), ( http::Method::GET, sandbox_path, @@ -7716,16 +8048,21 @@ mod tests { sandbox_api_version: Arc::new(OnceCell::new()), config: KubernetesComputeConfig::default(), operator_allowlist: None, + pool_targets: Arc::default(), + pool_reconcile_cursor: Arc::default(), }; driver .sandbox_api_version .set(SANDBOX_VERSION_V1BETA1) .expect("set test Sandbox API version"); - tokio::time::timeout(Duration::from_secs(1), driver.stop_sandbox("sandbox-1")) - .await - .expect("stop timed out") - .expect("resume stop from Suspending"); + tokio::time::timeout( + Duration::from_secs(1), + driver.stop_sandbox("logical-sandbox"), + ) + .await + .expect("stop timed out") + .expect("resume stop from Suspending"); assert!(steps.lock().unwrap().is_empty()); } @@ -7749,6 +8086,11 @@ mod tests { sandbox.metadata.namespace = Some("openshell".to_string()); sandbox.metadata.resource_version = Some("42".to_string()); sandbox.metadata.annotations = Some(BTreeMap::from([ + (LABEL_SANDBOX_ID.to_string(), "logical-sandbox".to_string()), + ( + "openshell.ai/warm-pair-id".to_string(), + "sandbox-1".to_string(), + ), ( ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_OPERATION.to_string(), "stop".to_string(), @@ -7806,13 +8148,15 @@ mod tests { sandbox_api_version: Arc::new(OnceCell::new()), config: KubernetesComputeConfig::default(), operator_allowlist: None, + pool_targets: Arc::default(), + pool_reconcile_cursor: Arc::default(), }; driver .reconcile_sandbox_runtime_control_release( &lookup_api, &sandbox, - "sandbox-1", + "logical-sandbox", "openshell", "sandbox-cr", ) @@ -9296,6 +9640,43 @@ mod tests { ); } + #[test] + fn released_workload_requires_recorded_identity_and_preserves_isolation() { + let params = SandboxPodParams { + sandbox_id: "physical-pair", + ..Default::default() + }; + let cr = sandbox_to_k8s_spec_for_test(Some(&SandboxSpec::default()), ¶ms); + let mut pod: Pod = serde_json::from_value(cr["spec"]["podTemplate"].clone()).unwrap(); + pod.metadata.uid = Some("prepared-workload".into()); + pod.metadata.owner_references = Some(vec![OwnerReference { + api_version: format!("{SANDBOX_GROUP}/{SANDBOX_VERSION_V1ALPHA1}"), + kind: SANDBOX_KIND.into(), + name: "parent".into(), + uid: "parent-uid".into(), + controller: Some(true), + block_owner_deletion: Some(false), + }]); + let validate = |pod: &Pod, prepared_uid: Option<&str>| { + KubernetesComputeDriver::validate_capability_free_workload_pod( + pod, + "parent-uid", + params.sandbox_id, + params.sandbox_uid, + params.sandbox_gid, + params.sandbox_secret_name, + prepared_uid, + ) + }; + validate(&pod, None).unwrap(); + pod.spec.as_mut().unwrap().scheduling_gates = None; + assert!(validate(&pod, None).is_err()); + assert!(validate(&pod, Some("other-workload")).is_err()); + validate(&pod, Some("prepared-workload")).unwrap(); + pod.spec.as_mut().unwrap().host_network = Some(true); + assert!(validate(&pod, Some("prepared-workload")).is_err()); + } + // ----------------------------------------------------------------------- // User namespace tests // ----------------------------------------------------------------------- diff --git a/crates/openshell-driver-kubernetes/src/grpc.rs b/crates/openshell-driver-kubernetes/src/grpc.rs index 3427b81f3b..ddc177944a 100644 --- a/crates/openshell-driver-kubernetes/src/grpc.rs +++ b/crates/openshell-driver-kubernetes/src/grpc.rs @@ -52,20 +52,51 @@ impl ComputeDriverService { #[tonic::async_trait] impl ComputeDriver for ComputeDriverService { + async fn select_warm_pair( + &self, + request: Request, + ) -> Result, Status> { + let _ = &request; + Ok(Response::new( + self.driver.select_warm_pair(request.into_inner()).await?, + )) + } + + async fn sync_warm_pools( + &self, + _request: Request, + ) -> Result, Status> { + self.driver.sync_warm_pools(_request.into_inner()).await?; + Ok(Response::new( + openshell_core::proto::compute::v1::SyncWarmPoolsResponse {}, + )) + } + async fn authenticate_sandbox( &self, request: Request, ) -> Result, Status> { - self.rpc_tracer - .trace(openshell_otel::rpc::AUTHENTICATE_SANDBOX, async { - let credential = request.into_inner().credential; - if credential.is_empty() { - return Err(Status::invalid_argument("credential is required")); - } - let sandbox_id = self.driver.authenticate_sandbox(&credential).await?; - Ok(Response::new(AuthenticateSandboxResponse { sandbox_id })) - }) - .await + Box::pin( + self.rpc_tracer + .trace(openshell_otel::rpc::AUTHENTICATE_SANDBOX, async { + let request = request.into_inner(); + let credential = request.credential; + if credential.is_empty() { + return Err(Status::invalid_argument("credential is required")); + } + if request.supervisor_registration { + return Box::pin(self.driver.authenticate_supervisor(&credential)) + .await + .map(Response::new); + } + let sandbox_id = self.driver.authenticate_sandbox(&credential).await?; + Ok(Response::new(AuthenticateSandboxResponse { + sandbox_id, + registration: None, + })) + }), + ) + .await } async fn get_capabilities( @@ -149,10 +180,14 @@ impl ComputeDriver for ComputeDriverService { Box::pin( self.rpc_tracer .trace(openshell_otel::rpc::CREATE_SANDBOX, async { + let request = request.into_inner(); let sandbox = request - .into_inner() .sandbox .ok_or_else(|| Status::invalid_argument("sandbox is required"))?; + if let Some(candidate) = request.warm_pair { + self.driver.claim_warm_pair(&sandbox, &candidate).await?; + return Ok(Response::new(CreateSandboxResponse {})); + } self.driver .create_sandbox(&sandbox) .await @@ -220,6 +255,18 @@ impl ComputeDriver for ComputeDriverService { if request.sandbox_id.is_empty() { return Err(Status::invalid_argument("sandbox_id is required")); } + if let Some(candidate) = &request.warm_pair { + self.driver + .cancel_warm_pair( + &request.sandbox_id, + candidate, + request.only_unassigned_pair, + ) + .await?; + } + if request.only_unassigned_pair { + return Ok(Response::new(DeleteSandboxResponse { deleted: true })); + } let deleted = self .driver .delete_sandbox(&request.sandbox_id) diff --git a/crates/openshell-driver-kubernetes/src/main.rs b/crates/openshell-driver-kubernetes/src/main.rs index fbcaf1d5f6..14164ca639 100644 --- a/crates/openshell-driver-kubernetes/src/main.rs +++ b/crates/openshell-driver-kubernetes/src/main.rs @@ -135,6 +135,24 @@ struct Args { )] sandbox_runtime_boundary_port: u16, + /// Prepare unassigned workload/proxy pairs for eligible templates. + #[arg(long, env = "OPENSHELL_K8S_WARM_POOL_ENABLED", default_value_t = false)] + warm_pool_enabled: bool, + #[arg(long, env = "OPENSHELL_K8S_WARM_POOL_MAX_PAIRS", default_value_t = 10)] + warm_pool_max_pairs: u32, + #[arg( + long, + env = "OPENSHELL_K8S_WARM_POOL_PREPARATION_TIMEOUT_SECONDS", + default_value_t = 300 + )] + warm_pool_preparation_timeout_seconds: u64, + #[arg( + long, + env = "OPENSHELL_K8S_WARM_POOL_MAX_IDLE_SECONDS", + default_value_t = 3600 + )] + warm_pool_max_idle_seconds: u64, + /// Corporate HTTP forward proxy for policy-approved TLS CONNECT egress. #[arg(long, env = "OPENSHELL_UPSTREAM_PROXY")] https_proxy: Option, @@ -253,6 +271,12 @@ async fn main() -> Result<()> { .supervisor_image .unwrap_or_else(openshell_core::config::default_supervisor_image), supervisor_image_pull_policy: args.supervisor_image_pull_policy, + warm_pool: openshell_driver_kubernetes::config::WarmPoolConfig { + enabled: args.warm_pool_enabled, + max_pairs: args.warm_pool_max_pairs, + preparation_timeout_seconds: args.warm_pool_preparation_timeout_seconds, + max_idle_seconds: args.warm_pool_max_idle_seconds, + }, sandbox_runtime: KubernetesSandboxRuntimeConfig { network_policy_enforced: args.sandbox_runtime_network_policy_enforced, boundary_port: args.sandbox_runtime_boundary_port, diff --git a/crates/openshell-driver-kubernetes/src/sandbox_runtime.rs b/crates/openshell-driver-kubernetes/src/sandbox_runtime.rs index 791cbda3c3..9093fee74d 100644 --- a/crates/openshell-driver-kubernetes/src/sandbox_runtime.rs +++ b/crates/openshell-driver-kubernetes/src/sandbox_runtime.rs @@ -29,7 +29,6 @@ pub const BOUNDARY_CONFIG_KEY: &str = "boundary.json"; pub const BACKEND_DESCRIPTOR_KEY: &str = "runtime-descriptor.json"; pub const BOUNDARY_CERTIFICATE_KEY: &str = "tls.crt"; pub const BOUNDARY_PRIVATE_KEY: &str = "tls.key"; -pub const SUPERVISOR_AUTH_BUNDLE_KEY: &str = "auth.json"; pub const PROXY_CA_CERTIFICATE_KEY: &str = "proxy-ca.crt"; pub const PROXY_CA_PRIVATE_KEY: &str = "proxy-ca.key"; pub const SANDBOX_BOOTSTRAP_INPUT_PATH: &str = "/.openshell/bootstrap-input"; @@ -37,7 +36,6 @@ pub const BOUNDARY_CONFIG_PATH: &str = "/.openshell/state/bootstrap/boundary.jso pub const BOUNDARY_CERTIFICATE_PATH: &str = "/.openshell/state/bootstrap/tls.crt"; pub const BOUNDARY_PRIVATE_KEY_PATH: &str = "/.openshell/state/bootstrap/tls.key"; pub const BACKEND_DESCRIPTOR_PATH: &str = "/.openshell/supervisor/runtime-descriptor.json"; -pub const SUPERVISOR_AUTH_BUNDLE_PATH: &str = "/.openshell/supervisor/auth.json"; pub const PROXY_CA_CERTIFICATE_PATH: &str = "/.openshell/supervisor/proxy-ca.crt"; pub const PROXY_CA_PRIVATE_KEY_PATH: &str = "/.openshell/supervisor/proxy-ca.key"; pub const CONTROL_HEALTH_SOCKET_PATH: &str = "/run/openshell/health.sock"; @@ -272,8 +270,7 @@ pub fn supervisor_pod( "/openshell-supervisor".to_string(), "--backend-descriptor-file".to_string(), BACKEND_DESCRIPTOR_PATH.to_string(), - "--auth-bundle-file".to_string(), - SUPERVISOR_AUTH_BUNDLE_PATH.to_string(), + "--register-supervisor".to_string(), "--workdir".to_string(), "/sandbox".to_string(), "--health-socket-path".to_string(), @@ -463,7 +460,6 @@ pub fn supervisor_bootstrap_secret( names: &SandboxRuntimeNames, sandbox_id: &str, backend_descriptor: Vec, - supervisor_auth_bundle: Vec, proxy_ca_certificate: Vec, proxy_ca_private_key: Vec, owner: OwnerReference, @@ -481,10 +477,6 @@ pub fn supervisor_bootstrap_secret( BACKEND_DESCRIPTOR_KEY.to_string(), ByteString(backend_descriptor), ), - ( - SUPERVISOR_AUTH_BUNDLE_KEY.to_string(), - ByteString(supervisor_auth_bundle), - ), ( PROXY_CA_CERTIFICATE_KEY.to_string(), ByteString(proxy_ca_certificate), @@ -791,7 +783,6 @@ mod tests { Vec::new(), Vec::new(), Vec::new(), - Vec::new(), owner(), ); assert_eq!( @@ -809,7 +800,6 @@ mod tests { std::collections::BTreeSet::from([ PROXY_CA_CERTIFICATE_KEY.to_string(), PROXY_CA_PRIVATE_KEY.to_string(), - SUPERVISOR_AUTH_BUNDLE_KEY.to_string(), BACKEND_DESCRIPTOR_KEY.to_string(), ]) ); diff --git a/crates/openshell-driver-kubernetes/src/warm_pool.rs b/crates/openshell-driver-kubernetes/src/warm_pool.rs new file mode 100644 index 0000000000..7b7cb35145 --- /dev/null +++ b/crates/openshell-driver-kubernetes/src/warm_pool.rs @@ -0,0 +1,2029 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Lease-elected preparation of single-use pairs. Sandbox metadata is authoritative. +use super::{ + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT, ANNOTATION_SANDBOX_RUNTIME_GENERATION, + ANNOTATION_SANDBOX_RUNTIME_SUPERVISOR_UID, ANNOTATION_SANDBOX_RUNTIME_WORKLOAD_UID, + KUBE_API_TIMEOUT, KubernetesComputeDriver, KubernetesDriverError, LABEL_GATEWAY_ID, + LABEL_MANAGED_BY, LABEL_MANAGED_BY_VALUE, LABEL_SANDBOX_ID, LABEL_SANDBOX_NAME, + LABEL_SANDBOX_WORKSPACE, Sandbox, SandboxRuntimeBootstrapPhase, SandboxRuntimeNames, + annotation_or_label, decode_launch_authentication, gateway_verification_keys, + random_sandbox_runtime_token, sandbox_runtime_bootstrap_phase, + validate_kubernetes_dns1123_label, +}; +use crate::isolation::{BOUNDARY_PAIR_LABEL, BOUNDARY_ROLE_LABEL}; +use k8s_openapi::api::coordination::v1::{Lease, LeaseSpec}; +use k8s_openapi::api::core::v1::{Pod, Service}; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::MicroTime; +use kube::api::Preconditions; +use kube::api::{DeleteParams, ListParams, Patch, PatchParams, PostParams}; +use kube::core::{DynamicObject, ObjectMeta}; +use kube::{Api, Error as KubeError}; +use openshell_core::SandboxSessionId; +use openshell_core::jwt::{CredentialEpoch, SessionRotation, SessionVerificationKey}; +use openshell_core::proto::compute::v1::{SyncWarmPoolsRequest, WarmPoolTarget}; +use openshell_core::sandbox_generation::SandboxGenerationId; +use serde::Deserialize; +use serde::Serialize; +use sha2::{Digest, Sha256}; +use std::{ + collections::{BTreeMap, HashSet}, + sync::atomic::Ordering, + time::Duration, +}; +use tracing::warn; + +pub(super) const POOL: &str = "openshell.ai/warm-pool"; +pub(super) const SERVICE_UID: &str = "openshell.ai/boundary-service-uid"; +const STATE: &str = "openshell.ai/warm-pool-state"; +const FINGERPRINT: &str = "openshell.ai/warm-pool-fingerprint"; +const PAIR: &str = "openshell.ai/warm-pair-id"; +const AUTH: &str = "openshell.ai/warm-pair-preparation"; +const REGISTERED: &str = "openshell.ai/warm-proxy-registered-at-ms"; +const TEMPLATE_NAME: &str = "openshell.ai/template-name"; +const TEMPLATE_ID: &str = "openshell.ai/template-id"; +const LEASE_SECONDS: i32 = 30; + +#[derive(Clone)] +pub(super) struct Snapshot { + request: SyncWarmPoolsRequest, + received: std::time::Instant, +} + +/// Public trust and physical runtime identity, without any tenant credentials. +#[derive(Clone, Serialize, Deserialize)] +pub(super) struct PreparationAuthentication { + pub gateway_id: String, + pub verification_keys: Vec, + pub runtime_generation: SandboxGenerationId, + pub session_id: SandboxSessionId, + pub session_rotation: SessionRotation, + pub auth_epoch: CredentialEpoch, +} +impl PreparationAuthentication { + pub(super) fn from_sandbox(sandbox: &Sandbox) -> Result { + let encoded = sandbox + .spec + .as_ref() + .map(|s| s.launch_authentication.as_slice()) + .unwrap_or_default(); + let auth = decode_launch_authentication(encoded)?; + Ok(Self { + gateway_id: auth.gateway_id, + verification_keys: auth.verification_keys, + runtime_generation: auth.supervisor.runtime_generation, + session_id: auth.supervisor.session_id, + session_rotation: auth.supervisor.session_rotation, + auth_epoch: auth.supervisor.auth_epoch, + }) + } +} + +pub(super) struct Preparation { + template_id: String, + template_name: String, + fingerprint: String, + pair_id: String, + pub authentication: PreparationAuthentication, +} +impl Preparation { + pub(super) fn decorate(&self, object: &mut DynamicObject, auth: &PreparationAuthentication) { + let labels = object.metadata.labels.get_or_insert_default(); + labels.remove(LABEL_SANDBOX_ID); + labels.insert(POOL.into(), self.template_id.clone()); + labels.insert(TEMPLATE_ID.into(), self.template_id.clone()); + if !self.template_name.is_empty() { + labels.insert(TEMPLATE_NAME.into(), self.template_name.clone()); + } + labels.insert(STATE.into(), "preparing".into()); + let annotations = object.metadata.annotations.get_or_insert_default(); + annotations.remove(LABEL_SANDBOX_ID); + annotations.insert(PAIR.into(), self.pair_id.clone()); + annotations.insert(FINGERPRINT.into(), self.fingerprint.clone()); + annotations.insert( + AUTH.into(), + serde_json::to_string(auth).expect("preparation serializes"), + ); + } +} + +pub(super) fn is_pool_pair(object: &DynamicObject) -> bool { + object + .metadata + .labels + .as_ref() + .is_some_and(|labels| labels.contains_key(POOL)) + && annotation_or_label(object, LABEL_SANDBOX_ID).is_none_or(|id| id.is_empty()) +} + +impl KubernetesComputeDriver { + pub async fn select_warm_pair( + &self, + request: openshell_core::proto::compute::v1::SelectWarmPairRequest, + ) -> Result { + use openshell_core::proto::compute::v1::{SelectWarmPairResponse, WarmPairCandidate}; + let mut response = SelectWarmPairResponse::default(); + let sandbox = request + .sandbox + .ok_or_else(|| tonic::Status::invalid_argument("missing sandbox"))?; + let Some(snapshot) = self.pool_targets.read().await.clone() else { + return Ok(response); + }; + if !self.config.warm_pool.enabled || snapshot.received.elapsed() > Duration::from_mins(1) { + return Ok(response); + } + let Some(target) = snapshot.request.pools.iter().find(|target| { + target.template_id == request.template_id && target.workspace == sandbox.workspace + }) else { + return Ok(response); + }; + if !self.pool_shape_matches(target, &sandbox) { + return Ok(response); + } + let fingerprint = self + .pool_fingerprint(target, &snapshot.request) + .map_err(|e| tonic::Status::internal(e.to_string()))?; + let namespace = self + .config + .namespace_for_workspace(&sandbox.workspace, self.operator_allowlist.as_ref()) + .map_err(tonic::Status::invalid_argument)?; + let api = self + .supported_agent_sandbox_api(self.client.clone(), &namespace) + .await + .map_err(tonic::Status::unavailable)?; + let selector = format!( + "{LABEL_MANAGED_BY}={LABEL_MANAGED_BY_VALUE},{LABEL_GATEWAY_ID}={},{POOL}={},{}=ready", + self.config.gateway_id, request.template_id, STATE + ); + let inventory = api + .api + .list(&ListParams::default().labels(&selector)) + .await + .map_err(|_| tonic::Status::unavailable("warm inventory unavailable"))?; + for object in inventory { + if !is_pool_pair(&object) + || object + .metadata + .uid + .as_ref() + .is_some_and(|uid| request.excluded_candidate_uids.contains(uid)) + || object.metadata.deletion_timestamp.is_some() + || annotation_or_label(&object, LABEL_SANDBOX_WORKSPACE).as_deref() + != Some(&sandbox.workspace) + || annotation_or_label(&object, FINGERPRINT).as_deref() != Some(&fingerprint) + || !recent_registration(&object) + || !within_idle_lifetime(&object, self.config.warm_pool.max_idle_seconds) + { + continue; + } + response.candidate = Some(WarmPairCandidate { + namespace: namespace.clone(), + name: object.metadata.name.clone().unwrap_or_default(), + uid: object.metadata.uid.clone().unwrap_or_default(), + template_id: request.template_id.clone(), + fingerprint: fingerprint.clone(), + runtime_generation: annotation_or_label( + &object, + ANNOTATION_SANDBOX_RUNTIME_GENERATION, + ) + .unwrap_or_default(), + }); + break; + } + Ok(response) + } + + fn pool_shape_matches(&self, target: &WarmPoolTarget, sandbox: &Sandbox) -> bool { + // Only command/TTY/attachment and gateway-managed policy are late-bound. + // Image, resources and environment remain part of the immutable workload shape. + let normalize = |spec: Option| { + spec.map(|mut spec| { + spec.command.clear(); + spec.tty = false; + spec.await_main_process_attachment = false; + // Kubernetes pins UID/GID from driver configuration and namespace SCC. + spec.workload_identity = None; + spec.policy = None; + spec.sandbox_token.clear(); + spec.launch_authentication.clear(); + if let Some(template) = spec.template.as_mut() + && template.image.trim().is_empty() + { + template.image.clone_from(&self.config.default_image); + } + spec + }) + }; + normalize(target.spec.clone()) == normalize(sandbox.spec.clone()) + } + + pub async fn claim_warm_pair( + &self, + sandbox: &Sandbox, + candidate: &openshell_core::proto::compute::v1::WarmPairCandidate, + ) -> Result<(), tonic::Status> { + let namespace = self + .config + .namespace_for_workspace(&sandbox.workspace, self.operator_allowlist.as_ref()) + .map_err(tonic::Status::invalid_argument)?; + if namespace != candidate.namespace { + return Err(tonic::Status::aborted("warm pair namespace changed")); + } + let api = self + .supported_agent_sandbox_api(self.client.clone(), &namespace) + .await + .map_err(tonic::Status::unavailable)?; + for _ in 0..4 { + let object = api + .api + .get_opt(&candidate.name) + .await + .map_err(|_| tonic::Status::unavailable("warm claim observation unavailable"))? + .ok_or_else(|| tonic::Status::aborted("warm pair disappeared"))?; + if object.metadata.uid.as_deref() != Some(&candidate.uid) + || object.metadata.deletion_timestamp.is_some() + { + return Err(tonic::Status::aborted("warm pair was replaced or retired")); + } + if annotation_or_label(&object, LABEL_SANDBOX_ID).as_deref() == Some(&sandbox.id) { + self.schedule_pair_metadata(object); + return Ok(()); + } + if !is_pool_pair(&object) + || annotation_or_label(&object, STATE).as_deref() != Some("ready") + || annotation_or_label(&object, POOL).as_deref() != Some(&candidate.template_id) + || annotation_or_label(&object, FINGERPRINT).as_deref() + != Some(&candidate.fingerprint) + || annotation_or_label(&object, ANNOTATION_SANDBOX_RUNTIME_GENERATION).as_deref() + != Some(&candidate.runtime_generation) + || annotation_or_label(&object, LABEL_SANDBOX_WORKSPACE).as_deref() + != Some(&sandbox.workspace) + || annotation_or_label(&object, LABEL_GATEWAY_ID).as_deref() + != Some(&self.config.gateway_id) + || !recent_registration(&object) + || !within_idle_lifetime(&object, self.config.warm_pool.max_idle_seconds) + { + return Err(tonic::Status::aborted("warm pair is no longer eligible")); + } + let snapshot = self + .pool_targets + .read() + .await + .clone() + .ok_or_else(|| tonic::Status::unavailable("pool targets unavailable"))?; + if snapshot.received.elapsed() > Duration::from_mins(1) { + return Err(tonic::Status::unavailable("pool targets stale")); + } + let target = snapshot.request.pools.iter().find(|t| { + t.template_id == candidate.template_id && t.workspace == sandbox.workspace + }); + if !self.config.warm_pool.enabled + || target.is_none_or(|target| { + !self.pool_shape_matches(target, sandbox) + || self + .pool_fingerprint(target, &snapshot.request) + .ok() + .as_deref() + != Some(&candidate.fingerprint) + }) + { + return Err(tonic::Status::aborted("warm pool revision changed")); + } + let main = openshell_core::sandbox_env::MainProcessConfig::encode_driver_spec( + sandbox.spec.as_ref(), + ) + .map_err(|_| tonic::Status::invalid_argument("invalid main process configuration"))?; + let patch = serde_json::json!({"metadata": {"resourceVersion": object.metadata.resource_version, + "labels": {STATE: null, POOL: null, LABEL_SANDBOX_ID: sandbox.id, LABEL_SANDBOX_NAME: sandbox.name}, + "annotations": {LABEL_SANDBOX_ID: sandbox.id, LABEL_SANDBOX_NAME: sandbox.name, + super::ANNOTATION_SANDBOX_RUNTIME_MAIN_PROCESS_SPEC: main, + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT: openshell_core::time::now_ms().to_string()}}}); + match api + .api + .patch( + &candidate.name, + &PatchParams::default(), + &Patch::Merge(&patch), + ) + .await + { + Ok(claimed) => { + self.schedule_pair_metadata(claimed); + return Ok(()); + } + Err(KubeError::Api(e)) if e.code == 409 => {} + Err(_) => { + return Err(tonic::Status::unavailable( + "warm claim outcome is uncertain", + )); + } + } + } + Err(tonic::Status::unavailable("warm claim remains contended")) + } + + fn schedule_pair_metadata(&self, object: DynamicObject) { + let driver = self.clone(); + tokio::spawn(async move { + driver.reconcile_pair_metadata(&object).await; + }); + } + + /// Derive logical labels and the SPIFFE sandbox-ID annotation from the parent. + /// Physical pair selectors and registration resource bindings remain unchanged. + /// Repair metadata after claims and during lifecycle reconciliation. + pub(super) async fn reconcile_pair_metadata(&self, object: &DynamicObject) { + let mut labels = BTreeMap::new(); + let mut annotations = BTreeMap::new(); + for key in [TEMPLATE_ID, TEMPLATE_NAME] { + if let Some(value) = annotation_or_label(object, key).filter(|s| !s.is_empty()) { + labels.insert(key.to_string(), value); + } + } + if !is_pool_pair(object) { + for key in [LABEL_SANDBOX_ID, LABEL_SANDBOX_NAME] { + if let Some(value) = annotation_or_label(object, key).filter(|s| !s.is_empty()) { + if key == LABEL_SANDBOX_ID { + annotations.insert(key.to_string(), value.clone()); + } + labels.insert(key.to_string(), value); + } + } + } + if labels.is_empty() { + return; + } + if object.metadata.deletion_timestamp.is_some() { + return; + } + let namespace = object + .metadata + .namespace + .as_deref() + .unwrap_or(&self.config.namespace); + let pods = Api::::namespaced(self.client.clone(), namespace); + let id = resource_id(object); + let update = async { + let inventory = pods + .list( + &ListParams::default() + .labels(&format!("{BOUNDARY_PAIR_LABEL}={}", resource_id(object))), + ) + .await?; + for pod in inventory.items { + let role = pod + .metadata + .labels + .as_ref() + .and_then(|l| l.get(BOUNDARY_ROLE_LABEL)) + .map(String::as_str); + let expected_uid = match role { + Some("workload") => { + annotation_or_label(object, ANNOTATION_SANDBOX_RUNTIME_WORKLOAD_UID) + } + Some("supervisor") => { + annotation_or_label(object, ANNOTATION_SANDBOX_RUNTIME_SUPERVISOR_UID) + } + _ => None, + }; + if expected_uid.is_none() + || pod.metadata.uid != expected_uid + || pod.metadata.deletion_timestamp.is_some() + || (pod.metadata.labels.as_ref().is_some_and(|current| { + labels.iter().all(|(k, v)| current.get(k) == Some(v)) + }) && annotations.iter().all(|(k, v)| { + pod.metadata.annotations.as_ref().and_then(|a| a.get(k)) == Some(v) + })) + { + continue; + } + if let Some(pod_name) = &pod.metadata.name { + let mut patch = serde_json::json!({"metadata": { + "uid": pod.metadata.uid, + "resourceVersion": pod.metadata.resource_version, + "labels": labels + }}); + if !annotations.is_empty() { + patch["metadata"]["annotations"] = serde_json::json!(annotations); + } + pods.patch(pod_name, &PatchParams::default(), &Patch::Merge(&patch)) + .await?; + } + } + Ok::<_, KubeError>(()) + }; + match tokio::time::timeout(KUBE_API_TIMEOUT, update).await { + Ok(Ok(())) => {} + Ok(Err(error)) => { + warn!(sandbox_id = %id, %error, "could not reconcile sandbox metadata on pair Pods; will retry"); + } + Err(_) => { + warn!(sandbox_id = %id, "timed out reconciling sandbox metadata on pair Pods; will retry"); + } + } + } + + pub async fn cancel_warm_pair( + &self, + sandbox_id: &str, + candidate: &openshell_core::proto::compute::v1::WarmPairCandidate, + only_unassigned: bool, + ) -> Result<(), tonic::Status> { + if !self.accepts_auth_namespace(&candidate.namespace) { + return Err(tonic::Status::permission_denied("invalid pair namespace")); + } + let api = self + .supported_agent_sandbox_api(self.client.clone(), &candidate.namespace) + .await + .map_err(tonic::Status::unavailable)?; + for _ in 0..4 { + let Some(object) = api + .api + .get_opt(&candidate.name) + .await + .map_err(|_| tonic::Status::unavailable("pair cancellation unavailable"))? + else { + return Ok(()); + }; + if object.metadata.uid.as_deref() != Some(&candidate.uid) { + return Ok(()); + } + if annotation_or_label(&object, LABEL_GATEWAY_ID).as_deref() + != Some(&self.config.gateway_id) + { + return Err(tonic::Status::permission_denied("pair gateway mismatch")); + } + let assigned = annotation_or_label(&object, LABEL_SANDBOX_ID).unwrap_or_default(); + if !assigned.is_empty() && assigned != sandbox_id { + return Ok(()); + } + if assigned == sandbox_id && only_unassigned { + return Err(tonic::Status::failed_precondition( + "candidate already claimed; recover the assignment", + )); + } + // The same CAS used by claim fences any in-flight allocation before deletion. + let patch = serde_json::json!({"metadata":{"resourceVersion": object.metadata.resource_version,"labels":{STATE:"retiring"}}}); + let retiring = match api + .api + .patch( + &candidate.name, + &PatchParams::default(), + &Patch::Merge(&patch), + ) + .await + { + Ok(object) => object, + Err(KubeError::Api(e)) if e.code == 409 => continue, + Err(KubeError::Api(e)) if e.code == 404 => return Ok(()), + Err(_) => return Err(tonic::Status::unavailable("pair cancellation uncertain")), + }; + match api + .api + .delete( + &candidate.name, + &DeleteParams { + preconditions: Some(Preconditions { + uid: retiring.metadata.uid, + resource_version: retiring.metadata.resource_version, + }), + ..Default::default() + }, + ) + .await + { + Ok(_) => return Ok(()), + Err(KubeError::Api(e)) if e.code == 404 => return Ok(()), + Err(KubeError::Api(e)) if e.code == 409 => {} + Err(_) => return Err(tonic::Status::unavailable("pair deletion uncertain")), + } + } + Err(tonic::Status::unavailable("pair cancellation contended")) + } + + pub async fn sync_warm_pools( + &self, + mut request: SyncWarmPoolsRequest, + ) -> Result<(), tonic::Status> { + if request.gateway_id != self.config.gateway_id { + return Err(tonic::Status::invalid_argument("pool gateway mismatch")); + } + let keys: Vec = serde_json::from_slice(&request.verification_keys) + .map_err(|_| tonic::Status::invalid_argument("invalid pool verification keys"))?; + gateway_verification_keys(&keys) + .map_err(|_| tonic::Status::invalid_argument("invalid pool trust"))?; + if keys.is_empty() { + return Err(tonic::Status::invalid_argument("missing pool trust")); + } + if !self.config.warm_pool.enabled { + request.pools.clear(); + } + let mut seen = HashSet::new(); + for pool in &request.pools { + validate_kubernetes_dns1123_label(&pool.template_id, "pool template ID") + .map_err(tonic::Status::invalid_argument)?; + if !pool.template_name.is_empty() { + validate_kubernetes_dns1123_label(&pool.template_name, "pool template name") + .map_err(tonic::Status::invalid_argument)?; + } + if pool.spec.is_none() || !seen.insert((&pool.workspace, &pool.template_id)) { + return Err(tonic::Status::invalid_argument( + "invalid or duplicate pool target", + )); + } + } + // Namespace eligibility can change independently of stored templates. + // Keep refreshing the other workspaces when one leaves the allowlist. + request.pools.retain(|pool| { + match self + .config + .namespace_for_workspace(&pool.workspace, self.operator_allowlist.as_ref()) + { + Ok(_) => true, + Err(error) => { + warn!(workspace = %pool.workspace, template_id = %pool.template_id, %error, "skipping unavailable warm pool workspace"); + false + } + } + }); + *self.pool_targets.write().await = Some(Snapshot { + request, + received: std::time::Instant::now(), + }); + Ok(()) + } + + pub(super) fn spawn_pool_controller(&self, mut shutdown: tokio::sync::watch::Receiver) { + let driver = self.clone(); + tokio::spawn(async move { + let holder = random_sandbox_runtime_token(); + let mut interval = tokio::time::interval(Duration::from_secs(10)); + loop { + tokio::select! { _ = shutdown.changed() => break, _ = interval.tick() => {} } + if *shutdown.borrow() { + break; + } + let Some(snapshot) = driver.pool_targets.read().await.clone() else { + continue; + }; + if snapshot.received.elapsed() > Duration::from_mins(1) { + continue; + } + match driver.renew_pool_lease(&holder).await { + Ok(true) => {} + Ok(false) => continue, + Err(error) => { + warn!(%error, "warm pool election failed"); + continue; + } + } + // Renew concurrently; cancel preparation immediately on election uncertainty. + let reconcile = driver.reconcile_pools(&snapshot.request); + tokio::pin!(reconcile); + loop { + tokio::select! { + _ = shutdown.changed() => return, + result = &mut reconcile => { + if let Err(error) = result { warn!(%error, "warm pool reconciliation failed"); } + break; + } + _ = interval.tick() => { + let current = driver.pool_targets.read().await.clone(); + if current.as_ref().is_none_or(|current| current.received.elapsed() > Duration::from_mins(1) || current.request != snapshot.request) + || !matches!(driver.renew_pool_lease(&holder).await, Ok(true)) { break; } + } + } + } + } + }); + } + + async fn renew_pool_lease(&self, holder: &str) -> Result { + let leases: Api = Api::namespaced(self.client.clone(), &self.config.namespace); + let name = format!( + "os-pools-{:x}", + Sha256::digest(self.config.gateway_id.as_bytes()) + )[..48] + .to_string(); + let current = leases + .get_opt(&name) + .await + .map_err(KubernetesDriverError::from_kube)?; + let now = k8s_openapi::chrono::Utc::now(); + // Use the API timestamp type's elapsed time rather than lexical comparison. + let now_ms = openshell_core::time::now_ms(); + if let Some(spec) = current.as_ref().and_then(|l| l.spec.as_ref()) { + let expires = spec + .renew_time + .as_ref() + .map_or(0, |time| time.0.timestamp_millis()) + + i64::from(spec.lease_duration_seconds.unwrap_or(LEASE_SECONDS)) * 1000; + if spec.holder_identity.as_deref() != Some(holder) && expires > now_ms { + return Ok(false); + } + } + let lease = Lease { + metadata: ObjectMeta { + name: Some(name.clone()), + resource_version: current + .as_ref() + .and_then(|l| l.metadata.resource_version.clone()), + ..Default::default() + }, + spec: Some(LeaseSpec { + holder_identity: Some(holder.into()), + lease_duration_seconds: Some(LEASE_SECONDS), + renew_time: Some(MicroTime(now)), + ..Default::default() + }), + }; + let result = if current.is_some() { + leases.replace(&name, &PostParams::default(), &lease).await + } else { + leases.create(&PostParams::default(), &lease).await + }; + match result { + Ok(_) => Ok(true), + Err(KubeError::Api(e)) if e.code == 409 => Ok(false), + Err(e) => Err(KubernetesDriverError::from_kube(e)), + } + } + + async fn reconcile_pools( + &self, + request: &SyncWarmPoolsRequest, + ) -> Result<(), KubernetesDriverError> { + let lookup = self + .supported_sandbox_api_for_lookup(self.client.clone()) + .await + .map_err(KubernetesDriverError::Message)?; + // Spare pairs intentionally lack LABEL_SANDBOX_ID. The ordinary sandbox + // selector requires that label and would hide every unassigned pair. + let inventory = lookup + .api + .list(&ListParams::default().labels(&format!( + "{LABEL_MANAGED_BY}={LABEL_MANAGED_BY_VALUE},{LABEL_GATEWAY_ID}={},{POOL}", + self.config.gateway_id + ))) + .await + .map_err(KubernetesDriverError::from_kube)?; + let mut counts = BTreeMap::<(String, String), u32>::new(); + let mut total = 0; + let targets = self.ordered_pool_targets(request); + for object in inventory { + if !is_pool_pair(&object) { + continue; + } + if object.metadata.deletion_timestamp.is_some() { + total += 1; + continue; + } + let namespace = object + .metadata + .namespace + .as_deref() + .unwrap_or(&self.config.namespace); + if !self.accepts_auth_namespace(namespace) { + continue; + } + let workspace = + annotation_or_label(&object, LABEL_SANDBOX_WORKSPACE).unwrap_or_default(); + let template = annotation_or_label(&object, POOL).unwrap_or_default(); + let key = (workspace.clone(), template.clone()); + let target = targets + .iter() + .find(|p| p.workspace == workspace && p.template_id == template); + let age = openshell_core::time::now_ms() + - annotation_or_label(&object, ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT) + .and_then(|s| s.parse::().ok()) + .unwrap_or(0); + let state = annotation_or_label(&object, STATE).unwrap_or_default(); + let obsolete = !self.config.warm_pool.enabled + || target.is_none_or(|p| { + self.pool_fingerprint(p, request).ok().as_ref() + != annotation_or_label(&object, FINGERPRINT).as_ref() + }); + let expired = age + > i64::try_from(self.config.warm_pool.max_idle_seconds.saturating_mul(1000)) + .unwrap_or(i64::MAX); + let timed_out = state != "ready" + && age + > i64::try_from( + self.config + .warm_pool + .preparation_timeout_seconds + .saturating_mul(1000), + ) + .unwrap_or(i64::MAX); + let excess = total >= self.config.warm_pool.max_pairs + || counts.get(&key).copied().unwrap_or(0) >= target.map_or(0, |p| p.target); + if obsolete || expired || timed_out || excess || state == "retiring" { + total += 1; + if let Err(error) = self.retire_pair(&object).await { + warn!(pair = ?object.metadata.name, %error, "warm pair retirement will retry"); + } + continue; + } + total += 1; + *counts.entry(key).or_default() += 1; + if let Some(target) = target { + let remaining_ms = self + .config + .warm_pool + .preparation_timeout_seconds + .saturating_mul(1000) + .saturating_sub(u64::try_from(age).unwrap_or(0)); + let timeout = if state == "ready" { + KUBE_API_TIMEOUT + } else { + Duration::from_millis(remaining_ms.max(1)) + }; + match tokio::time::timeout(timeout, Box::pin(self.reconcile_pair(&object, target))) + .await + { + Ok(Ok(())) => {} + Ok(Err(error)) => { + warn!(pair = ?object.metadata.name, %error, "warm pair preparation will retry"); + } + Err(_) => { + warn!(pair = ?object.metadata.name, "warm pair reconciliation timed out"); + } + } + } + } + if !self.config.warm_pool.enabled { + return Ok(()); + } + // At most one successful pair per pass bounds handover overshoot. Failed + // attempts reserve a capacity slot because the parent CR may have been + // committed before the error became observable. Rotating target order + // prevents that conservative accounting from starving later targets. + for target in targets { + if total >= self.config.warm_pool.max_pairs { + break; + } + if counts + .get(&(target.workspace.clone(), target.template_id.clone())) + .copied() + .unwrap_or(0) + >= target.target + { + continue; + } + let verification_keys: Vec = + serde_json::from_slice(&request.verification_keys) + .map_err(|error| KubernetesDriverError::Message(error.to_string()))?; + let pair_id = random_sandbox_runtime_token()[..24].to_string(); + let preparation = match self.pool_preparation( + target, + request, + &pair_id, + &verification_keys, + ) { + Ok(preparation) => preparation, + Err(error) => { + warn!(workspace = %target.workspace, template_id = %target.template_id, %error, "warm pool target preparation rejected"); + continue; + } + }; + let sandbox = Sandbox { + id: pair_id.clone(), + name: format!("warm-{pair_id}"), + workspace: target.workspace.clone(), + spec: target.spec.clone(), + ..Default::default() + }; + let result = tokio::time::timeout( + Duration::from_secs(self.config.warm_pool.preparation_timeout_seconds), + Box::pin(self.create_sandbox_inner(&sandbox, Some(&preparation))), + ) + .await; + match result { + Ok(Ok(())) => break, + Ok(Err(error)) => { + warn!(workspace = %target.workspace, template_id = %target.template_id, %error, "warm pair creation will retry"); + } + Err(_) => { + warn!(workspace = %target.workspace, template_id = %target.template_id, "warm pair creation timed out and will retry"); + } + } + total += 1; + } + Ok(()) + } + + fn ordered_pool_targets<'a>( + &self, + request: &'a SyncWarmPoolsRequest, + ) -> Vec<&'a WarmPoolTarget> { + let mut targets = request.pools.iter().collect::>(); + targets.sort_by_key(|target| (&target.workspace, &target.template_id)); + if !targets.is_empty() { + let start = self.pool_reconcile_cursor.fetch_add(1, Ordering::Relaxed) % targets.len(); + targets.rotate_left(start); + } + targets + } + + fn pool_preparation( + &self, + target: &WarmPoolTarget, + request: &SyncWarmPoolsRequest, + pair_id: &str, + verification_keys: &[SessionVerificationKey], + ) -> Result { + Ok(Preparation { + template_id: target.template_id.clone(), + template_name: target.template_name.clone(), + fingerprint: self.pool_fingerprint(target, request)?, + pair_id: pair_id.to_string(), + authentication: PreparationAuthentication { + gateway_id: request.gateway_id.clone(), + verification_keys: verification_keys.to_vec(), + runtime_generation: SandboxGenerationId::parse(random_sandbox_runtime_token()) + .map_err(|error| KubernetesDriverError::Message(error.to_string()))?, + session_id: SandboxSessionId::new(), + session_rotation: SessionRotation::new(1).expect("nonzero"), + auth_epoch: CredentialEpoch::new(1).expect("nonzero"), + }, + }) + } + + fn pool_fingerprint( + &self, + target: &WarmPoolTarget, + request: &SyncWarmPoolsRequest, + ) -> Result { + // Canonical JSON avoids map iteration order changing a preparation revision. + use prost::Message; + let mut spec = target.spec.clone().unwrap_or_default(); + let environment = std::mem::take(&mut spec.environment) + .into_iter() + .collect::>(); + let template_environment = spec.template.as_mut().map(|t| { + std::mem::take(&mut t.environment) + .into_iter() + .collect::>() + }); + let template_labels = spec.template.as_mut().map(|t| { + std::mem::take(&mut t.labels) + .into_iter() + .collect::>() + }); + let mut config = serde_json::to_value(&self.config) + .map_err(|e| KubernetesDriverError::Message(e.to_string()))?; + config + .as_object_mut() + .expect("config object") + .remove("warm_pool"); + let encoded = serde_json::to_vec(&( + "pair-v1", + spec.encode_to_vec(), + environment, + template_environment, + template_labels, + config, + &request.gateway_id, + &request.verification_keys, + )) + .map_err(|e| KubernetesDriverError::Message(e.to_string()))?; + Ok(format!("{:x}", Sha256::digest(encoded))) + } + + async fn retire_pair(&self, object: &DynamicObject) -> Result<(), KubernetesDriverError> { + let namespace = object + .metadata + .namespace + .as_deref() + .unwrap_or(&self.config.namespace); + let api = self + .supported_agent_sandbox_api(self.client.clone(), namespace) + .await + .map_err(KubernetesDriverError::Message)?; + let name = object.metadata.name.as_deref().unwrap_or_default(); + // CAS competes with future allocation; never retry using a newer claimed object. + let patch = serde_json::json!({"metadata":{"resourceVersion":object.metadata.resource_version,"labels":{STATE:"retiring"}}}); + let retiring = match api + .api + .patch(name, &PatchParams::default(), &Patch::Merge(&patch)) + .await + { + Ok(object) => object, + Err(KubeError::Api(e)) if e.code == 409 || e.code == 404 => return Ok(()), + Err(e) => return Err(KubernetesDriverError::from_kube(e)), + }; + api.api + .delete( + name, + &DeleteParams { + preconditions: Some(Preconditions { + uid: retiring.metadata.uid, + resource_version: retiring.metadata.resource_version, + }), + ..Default::default() + }, + ) + .await + .map_err(KubernetesDriverError::from_kube)?; + Ok(()) + } + + async fn reconcile_pair( + &self, + object: &DynamicObject, + target: &WarmPoolTarget, + ) -> Result<(), KubernetesDriverError> { + let namespace = object + .metadata + .namespace + .as_deref() + .unwrap_or(&self.config.namespace); + let name = object.metadata.name.as_deref().unwrap_or_default(); + let pair_id = annotation_or_label(object, PAIR).ok_or_else(|| { + KubernetesDriverError::Message("pool pair lacks physical identity".into()) + })?; + let generation = + annotation_or_label(object, ANNOTATION_SANDBOX_RUNTIME_GENERATION).unwrap_or_default(); + let names = SandboxRuntimeNames::for_generation(&pair_id, &generation); + let api = self + .supported_agent_sandbox_api(self.client.clone(), namespace) + .await + .map_err(KubernetesDriverError::Message)?; + // Backfill display metadata on existing idle pairs without changing + // preparation fingerprints or replacing the prepared runtime. + let mut display_labels = BTreeMap::from([(TEMPLATE_ID, target.template_id.clone())]); + if !target.template_name.is_empty() { + display_labels.insert(TEMPLATE_NAME, target.template_name.clone()); + } + let updated; + let object = if display_labels + .iter() + .any(|(key, value)| annotation_or_label(object, key).as_ref() != Some(value)) + { + let patch = serde_json::json!({ + "metadata": {"resourceVersion": object.metadata.resource_version, "labels": display_labels} + }); + updated = api + .api + .patch(name, &PatchParams::default(), &Patch::Merge(&patch)) + .await + .map_err(KubernetesDriverError::from_kube)?; + &updated + } else { + object + }; + if sandbox_runtime_bootstrap_phase(object) == Some(SandboxRuntimeBootstrapPhase::Preparing) + { + let auth: PreparationAuthentication = + serde_json::from_str(&annotation_or_label(object, AUTH).unwrap_or_default()) + .map_err(|e| KubernetesDriverError::Message(e.to_string()))?; + let sandbox = Sandbox { + id: pair_id.clone(), + name: annotation_or_label(object, LABEL_SANDBOX_NAME).unwrap_or_default(), + workspace: target.workspace.clone(), + spec: target.spec.clone(), + ..Default::default() + }; + let (uid, gid, _) = self.resolve_sandbox_identity_in_namespace(namespace).await; + let main = openshell_core::sandbox_env::MainProcessConfig::encode_driver_spec( + sandbox.spec.as_ref(), + ) + .map_err(|e| KubernetesDriverError::Message(e.to_string()))?; + Box::pin(self.create_sandbox_runtime_companions( + &sandbox, + namespace, + name, + &api, + object, + &names, + &generation, + uid, + gid, + &main, + "info", + &auth, + )) + .await?; + return Ok(()); + } + self.reconcile_pair_metadata(object).await; + let pods: Api = Api::namespaced(self.client.clone(), namespace); + let workloads = pods + .list(&ListParams::default().labels(&format!("{BOUNDARY_ROLE_LABEL}=workload"))) + .await + .map_err(KubernetesDriverError::from_kube)?; + let uid = annotation_or_label(object, ANNOTATION_SANDBOX_RUNTIME_WORKLOAD_UID); + let workload_ready = workloads.items.iter().any(|pod| { + pod.metadata.uid == uid + && pod.metadata.deletion_timestamp.is_none() + && pod + .status + .as_ref() + .and_then(|s| s.conditions.as_ref()) + .is_some_and(|conditions| { + conditions + .iter() + .any(|c| c.type_ == "Ready" && c.status == "True") + }) + }); + let registered = annotation_or_label(object, REGISTERED) + .and_then(|v| v.parse::().ok()) + .is_some_and(|time| openshell_core::time::now_ms() - time < 45_000); + let proxy = pods + .get_opt(&names.supervisor_pod) + .await + .map_err(KubernetesDriverError::from_kube)?; + let proxy_live = proxy.is_some_and(|pod| { + pod.metadata.deletion_timestamp.is_none() + && pod.metadata.uid + == annotation_or_label(object, ANNOTATION_SANDBOX_RUNTIME_SUPERVISOR_UID) + && pod.status.as_ref().and_then(|s| s.phase.as_deref()) == Some("Running") + }); + let service = Api::::namespaced(self.client.clone(), namespace) + .get_opt(&names.boundary_service) + .await + .map_err(KubernetesDriverError::from_kube)?; + let service_ready = service + .filter(|service| service.metadata.uid == annotation_or_label(object, SERVICE_UID)) + .and_then(|service| service.spec) + .is_some_and(|spec| { + spec.cluster_ip + .as_deref() + .is_some_and(|ip| !ip.is_empty() && ip != "None") + && spec + .selector + .as_ref() + .and_then(|selector| selector.get(BOUNDARY_PAIR_LABEL)) + == Some(&pair_id) + }); + let fence_ready = self + .create_sandbox_runtime_fence(namespace, &names) + .await + .is_ok() + && Api::::namespaced( + self.client.clone(), + namespace, + ) + .get(&names.workload_policy) + .await + .is_ok_and(|fence| { + super::sandbox_runtime_namespace_fence_generation_matches(&fence, object) + }); + let ready = workload_ready && registered && proxy_live && service_ready && fence_ready; + let state = if ready { "ready" } else { "preparing" }; + if annotation_or_label(object, STATE).as_deref() != Some(state) { + let patch = serde_json::json!({"metadata":{"resourceVersion":object.metadata.resource_version,"labels":{STATE:state}}}); + match api + .api + .patch(name, &PatchParams::default(), &Patch::Merge(&patch)) + .await + { + Ok(_) => {} + Err(KubeError::Api(e)) if e.code == 409 => {} + Err(e) => return Err(KubernetesDriverError::from_kube(e)), + } + } + Ok(()) + } + + pub(super) async fn record_pool_registration( + &self, + api: &Api, + object: &DynamicObject, + ) -> Result<(), tonic::Status> { + if !is_pool_pair(object) { + return Ok(()); + } + if annotation_or_label(object, STATE).as_deref() == Some("retiring") { + return Err(tonic::Status::permission_denied("pair is retiring")); + } + let now = openshell_core::time::now_ms(); + if annotation_or_label(object, REGISTERED) + .and_then(|s| s.parse::().ok()) + .is_some_and(|time| now - time < 10_000) + { + return Ok(()); + } + let patch = serde_json::json!({"metadata":{"resourceVersion":object.metadata.resource_version,"annotations":{REGISTERED:now.to_string()}}}); + api.patch( + object.metadata.name.as_deref().unwrap_or_default(), + &PatchParams::default(), + &Patch::Merge(&patch), + ) + .await + .map_err(|_| tonic::Status::unavailable("pool registration changed; retry"))?; + Ok(()) + } +} + +/// Reuse only dependents of this exact Sandbox/Pod incarnation. +pub(super) async fn create_owned(api: &Api, desired: &K) -> Result +where + K: kube::Resource + + Clone + + std::fmt::Debug + + serde::de::DeserializeOwned + + Serialize + + Send + + Sync, +{ + let name = desired + .meta() + .name + .as_deref() + .ok_or_else(|| KubernetesDriverError::Message("dependent has no name".into()))?; + match api + .get_opt(name) + .await + .map_err(KubernetesDriverError::from_kube)? + { + Some(existing) => { + if existing.meta().deletion_timestamp.is_some() + || existing.meta().owner_references != desired.meta().owner_references + { + return Err(KubernetesDriverError::Precondition( + "pair dependent has conflicting ownership".into(), + )); + } + Ok(existing) + } + None => api + .create(&PostParams::default(), desired) + .await + .map_err(KubernetesDriverError::from_kube), + } +} + +fn within_idle_lifetime(object: &DynamicObject, max_idle_seconds: u64) -> bool { + annotation_or_label(object, ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT) + .and_then(|value| value.parse::().ok()) + .is_some_and(|started| { + let age = openshell_core::time::now_ms().saturating_sub(started); + u64::try_from(age).is_ok_and(|age| age <= max_idle_seconds.saturating_mul(1000)) + }) +} + +fn recent_registration(object: &DynamicObject) -> bool { + annotation_or_label(object, REGISTERED) + .and_then(|v| v.parse::().ok()) + .is_some_and(|time| (0..45_000).contains(&(openshell_core::time::now_ms() - time))) +} + +pub(super) fn resource_id(object: &DynamicObject) -> String { + annotation_or_label(object, PAIR) + .or_else(|| annotation_or_label(object, LABEL_SANDBOX_ID)) + .unwrap_or_default() +} + +#[cfg(test)] +mod tests { + use super::super::{ + ApiResource, Client, GroupVersionKind, KubernetesComputeConfig, OwnerReference, + SANDBOX_GROUP, SANDBOX_KIND, SANDBOX_VERSION_V1ALPHA1, Secret, sandbox_id_from_object, + }; + use super::*; + type Requests = Arc>>; + use http_body_util::BodyExt as _; + use openshell_core::proto::compute::v1::DriverSandboxSpec; + use std::sync::{Arc, Mutex}; + + fn api_driver(responses: Vec<(u16, serde_json::Value)>) -> (KubernetesComputeDriver, Requests) { + let requests = Arc::new(Mutex::new(Vec::new())); + let captured = requests.clone(); + let responses = Arc::new(Mutex::new(std::collections::VecDeque::from(responses))); + let service = tower::service_fn(move |request: http::Request| { + let captured = captured.clone(); + let responses = responses.clone(); + async move { + let (parts, body) = request.into_parts(); + let bytes = body.collect().await.unwrap().to_bytes(); + captured.lock().unwrap().push(( + parts.method.to_string(), + parts.uri.to_string(), + serde_json::from_slice(&bytes).unwrap_or_default(), + )); + let (status, value) = responses + .lock() + .unwrap() + .pop_front() + .expect("unexpected API request"); + Ok::<_, std::convert::Infallible>( + http::Response::builder() + .status(status) + .header("content-type", "application/json") + .body(http_body_util::Full::new(bytes::Bytes::from( + value.to_string(), + ))) + .unwrap(), + ) + } + }); + let mut driver = KubernetesComputeDriver::new_for_test(KubernetesComputeConfig::default()); + driver.client = Client::new(service, "default"); + (driver, requests) + } + + fn status(code: u16) -> (u16, serde_json::Value) { + ( + code, + serde_json::json!({"kind":"Status","apiVersion":"v1","status":"Failure","reason":"Conflict","message":"test","code":code}), + ) + } + + #[tokio::test] + async fn unavailable_workspace_does_not_block_pool_snapshot_refresh() { + use super::super::{OperatorNamespaceAllowlist, WorkspaceMode}; + + let (mut driver, requests) = api_driver(Vec::new()); + driver.config.workspace_mode = WorkspaceMode::Operator; + driver.config.warm_pool.enabled = true; + let allowlist = OperatorNamespaceAllowlist::new(); + allowlist.insert("available".into()); + allowlist.insert("removed".into()); + driver.operator_allowlist = Some(allowlist.clone()); + let request = SyncWarmPoolsRequest { + gateway_id: driver.config.gateway_id.clone(), + verification_keys: serde_json::to_vec(&[SessionVerificationKey { + key_id: "test-key".into(), + public_key_pem: rcgen::KeyPair::generate() + .unwrap() + .public_key_pem() + .into_bytes(), + }]) + .unwrap(), + pools: ["removed", "available"] + .into_iter() + .map(|workspace| WarmPoolTarget { + template_id: format!("template-{workspace}"), + workspace: workspace.into(), + target: 1, + spec: Some(DriverSandboxSpec::default()), + ..Default::default() + }) + .collect(), + }; + driver.sync_warm_pools(request.clone()).await.unwrap(); + allowlist.remove("removed"); + driver.pool_targets.write().await.as_mut().unwrap().received = std::time::Instant::now() + .checked_sub(Duration::from_mins(2)) + .expect("two minutes fits in Instant"); + driver.sync_warm_pools(request.clone()).await.unwrap(); + { + let snapshot = driver.pool_targets.read().await; + let snapshot = snapshot.as_ref().unwrap(); + assert_eq!(snapshot.request.pools, vec![request.pools[1].clone()]); + assert!(snapshot.received.elapsed() < Duration::from_mins(1)); + } + // An empty eligible snapshot must also replace stale inventory. + allowlist.remove("available"); + driver.sync_warm_pools(request.clone()).await.unwrap(); + assert!( + driver + .pool_targets + .read() + .await + .as_ref() + .unwrap() + .request + .pools + .is_empty() + ); + // Stored templates become eligible again when the namespace returns. + allowlist.insert("removed".into()); + driver.sync_warm_pools(request.clone()).await.unwrap(); + assert_eq!( + driver + .pool_targets + .read() + .await + .as_ref() + .unwrap() + .request + .pools, + vec![request.pools[0].clone()] + ); + assert!(requests.lock().unwrap().is_empty()); + } + + #[tokio::test] + async fn pair_metadata_reconciles_only_bound_pods_and_retries_failures() { + let parent: DynamicObject = serde_json::from_value(serde_json::json!({ + "apiVersion": "agents.x-k8s.io/v1alpha1", "kind": "Sandbox", + "metadata": {"name": "prepared", "namespace": "openshell", "annotations": { + LABEL_SANDBOX_ID: "logical", LABEL_SANDBOX_NAME: "friendly-name", + TEMPLATE_ID: "template-id", TEMPLATE_NAME: "example-template", + PAIR: "physical", ANNOTATION_SANDBOX_RUNTIME_WORKLOAD_UID: "workload-uid", + ANNOTATION_SANDBOX_RUNTIME_SUPERVISOR_UID: "supervisor-uid" + }} + })) + .unwrap(); + let pod = |name: &str, uid: &str, role: &str| { + serde_json::json!({ + "metadata": {"name": name, "uid": uid, "resourceVersion": "17", + "labels": {BOUNDARY_PAIR_LABEL: "physical", BOUNDARY_ROLE_LABEL: role}, + "annotations": {LABEL_SANDBOX_ID: "physical"}} + }) + }; + let workload = pod("workload", "workload-uid", "workload"); + let mut supervisor = pod("supervisor", "supervisor-uid", "supervisor"); + // Previously assigned Pods can already have correct labels while the + // SPIFFE annotation still points at the physical pair. + for (key, value) in [ + (LABEL_SANDBOX_ID, "logical"), + (LABEL_SANDBOX_NAME, "friendly-name"), + (TEMPLATE_ID, "template-id"), + (TEMPLATE_NAME, "example-template"), + ] { + supervisor["metadata"]["labels"][key] = value.into(); + } + let inventory = serde_json::json!({"apiVersion":"v1", "kind":"PodList", "items":[ + workload.clone(), supervisor.clone(), pod("replacement", "wrong-uid", "workload") + ]}); + let (driver, requests) = api_driver(vec![ + status(500), // Display metadata failures must be repairable. + (200, inventory), + (200, workload), + (200, supervisor), + ]); + driver.reconcile_pair_metadata(&parent).await; + driver.reconcile_pair_metadata(&parent).await; + let requests = requests.lock().unwrap(); + assert_eq!(requests.len(), 4); + for (_, _, patch) in &requests[2..] { + assert_eq!( + patch["metadata"]["labels"], + serde_json::json!({ + LABEL_SANDBOX_ID: "logical", LABEL_SANDBOX_NAME: "friendly-name", + TEMPLATE_ID: "template-id", TEMPLATE_NAME: "example-template" + }) + ); + assert_eq!(patch["metadata"]["resourceVersion"], "17"); + assert_eq!( + patch["metadata"]["annotations"], + serde_json::json!({LABEL_SANDBOX_ID: "logical"}) + ); + assert!(patch["spec"].is_null()); + } + assert_eq!(requests[2].2["metadata"]["uid"], "workload-uid"); + assert_eq!(requests[3].2["metadata"]["uid"], "supervisor-uid"); + } + + #[tokio::test] + async fn idle_pair_pods_get_template_labels_without_logical_sandbox_labels() { + let parent: DynamicObject = serde_json::from_value(serde_json::json!({ + "apiVersion": "agents.x-k8s.io/v1alpha1", "kind": "Sandbox", + "metadata": {"name": "prepared", "namespace": "openshell", + "labels": {POOL: "template-id", TEMPLATE_ID: "template-id", TEMPLATE_NAME: "example-template", LABEL_SANDBOX_NAME: "warm-physical"}, + "annotations": {PAIR: "physical", ANNOTATION_SANDBOX_RUNTIME_WORKLOAD_UID: "workload-uid"}} + })).unwrap(); + let pod = serde_json::json!({"metadata": {"name": "workload", "uid": "workload-uid", "resourceVersion": "3", + "labels": {BOUNDARY_ROLE_LABEL: "workload", BOUNDARY_PAIR_LABEL: "physical"}}}); + let (driver, requests) = api_driver(vec![ + ( + 200, + serde_json::json!({"apiVersion":"v1", "kind":"PodList", "items":[pod.clone()]}), + ), + (200, pod), + ]); + driver.reconcile_pair_metadata(&parent).await; + let requests = requests.lock().unwrap(); + assert_eq!( + requests[1].2["metadata"]["labels"], + serde_json::json!({ + TEMPLATE_ID: "template-id", TEMPLATE_NAME: "example-template" + }) + ); + assert!(requests[1].2["metadata"]["annotations"].is_null()); + } + + #[tokio::test] + async fn creation_uses_prepared_authentication_generation_for_metadata_and_secret_names() { + let policy = serde_json::json!({"apiVersion":"networking.k8s.io/v1","kind":"NetworkPolicy","metadata":{}}); + let mut absent = status(404); + absent.1["reason"] = "NotFound".into(); + let (mut driver, requests) = api_driver(vec![ + absent.clone(), + (201, policy.clone()), + absent.clone(), + (201, policy), + status(503), + ]); + driver.config.sandbox_uid = Some(10001); + driver + .sandbox_api_version + .set(SANDBOX_VERSION_V1ALPHA1) + .unwrap(); + let preparation = Preparation { + template_id: "template".into(), + template_name: "example-template".into(), + fingerprint: "revision".into(), + pair_id: "physical".into(), + authentication: PreparationAuthentication { + gateway_id: "openshell".into(), + verification_keys: Vec::new(), + runtime_generation: SandboxGenerationId::parse("prepared-generation").unwrap(), + session_id: SandboxSessionId::new(), + session_rotation: SessionRotation::new(1).unwrap(), + auth_epoch: CredentialEpoch::new(1).unwrap(), + }, + }; + let sandbox = Sandbox { + id: "physical".into(), + name: "warm-physical".into(), + workspace: "default".into(), + ..Default::default() + }; + // Stop at the parent POST: inspect the real creation payload without provisioning Pods. + let error = driver + .create_sandbox_inner(&sandbox, Some(&preparation)) + .await + .unwrap_err(); + let requests = requests.lock().unwrap(); + assert_eq!( + requests.len(), + 5, + "creation failed before parent POST: {error}; requests: {requests:?}" + ); + let (method, uri, object) = &requests[4]; + assert_eq!(method, "POST"); + assert!(uri.split('?').next().unwrap().ends_with("/sandboxes")); + assert_eq!( + object["metadata"]["labels"][TEMPLATE_NAME], + "example-template" + ); + assert_eq!(object["metadata"]["labels"][TEMPLATE_ID], "template"); + let auth: PreparationAuthentication = + serde_json::from_str(object["metadata"]["annotations"][AUTH].as_str().unwrap()) + .unwrap(); + assert_eq!( + object["metadata"]["annotations"][ANNOTATION_SANDBOX_RUNTIME_GENERATION], + auth.runtime_generation.as_str() + ); + let names = + SandboxRuntimeNames::for_generation(&sandbox.id, auth.runtime_generation.as_str()); + assert!( + object["spec"]["podTemplate"]["spec"]["volumes"] + .as_array() + .unwrap() + .iter() + .any(|volume| volume["secret"]["secretName"] == names.sandbox_secret) + ); + } + + async fn claim_fixture( + responses: impl FnOnce(&serde_json::Value) -> Vec<(u16, serde_json::Value)>, + ) -> ( + KubernetesComputeDriver, + Requests, + Sandbox, + openshell_core::proto::compute::v1::WarmPairCandidate, + ) { + use openshell_core::proto::compute::v1::{DriverSandboxSpec, WarmPairCandidate}; + let target = WarmPoolTarget { + template_id: "template".into(), + template_name: "example-template".into(), + workspace: "default".into(), + target: 1, + spec: Some(DriverSandboxSpec::default()), + }; + let snapshot = SyncWarmPoolsRequest { + pools: vec![target.clone()], + gateway_id: "openshell".into(), + verification_keys: b"[]".to_vec(), + }; + let mut config = KubernetesComputeConfig::default(); + config.warm_pool.enabled = true; + let fingerprint = KubernetesComputeDriver::new_for_test(config.clone()) + .pool_fingerprint(&target, &snapshot) + .unwrap(); + let object = serde_json::json!({"apiVersion":"agents.x-k8s.io/v1alpha1","kind":"Sandbox","metadata":{ + "name":"warm-example","namespace":"openshell","uid":"pair-uid","resourceVersion":"17", + "labels":{LABEL_MANAGED_BY:LABEL_MANAGED_BY_VALUE,LABEL_GATEWAY_ID:"openshell",LABEL_SANDBOX_WORKSPACE:"default",POOL:"template",STATE:"ready"}, + "annotations":{PAIR:"physical",FINGERPRINT:fingerprint, ANNOTATION_SANDBOX_RUNTIME_GENERATION:"generation",REGISTERED:openshell_core::time::now_ms().to_string(),ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT:openshell_core::time::now_ms().to_string()} + }}); + let (mut driver, requests) = api_driver(responses(&object)); + driver.config = config; + driver + .sandbox_api_version + .set(SANDBOX_VERSION_V1ALPHA1) + .unwrap(); + *driver.pool_targets.write().await = Some(Snapshot { + request: snapshot, + received: std::time::Instant::now(), + }); + let sandbox = Sandbox { + id: "logical".into(), + name: "example".into(), + workspace: "default".into(), + spec: target.spec, + ..Default::default() + }; + let candidate = WarmPairCandidate { + namespace: "openshell".into(), + name: "warm-example".into(), + uid: "pair-uid".into(), + template_id: "template".into(), + fingerprint, + runtime_generation: "generation".into(), + }; + (driver, requests, sandbox, candidate) + } + + #[tokio::test] + async fn selection_skips_targets_reserved_by_another_allocation() { + let (driver, requests, sandbox, _) = claim_fixture(|object| { + let mut second = object.clone(); + second["metadata"]["uid"] = "second-uid".into(); + second["metadata"]["name"] = "second-pair".into(); + vec![( + 200, + serde_json::json!({ + "apiVersion": "agents.x-k8s.io/v1alpha1", "kind": "SandboxList", "metadata": {}, + "items": [object, second] + }), + )] + }) + .await; + let response = driver + .select_warm_pair(openshell_core::proto::compute::v1::SelectWarmPairRequest { + template_id: "template".into(), + sandbox: Some(sandbox), + excluded_candidate_uids: vec!["pair-uid".into()], + }) + .await + .unwrap(); + assert_eq!(response.candidate.unwrap().uid, "second-uid"); + assert_eq!( + requests.lock().unwrap().len(), + 1, + "selection remains read-only" + ); + } + + #[tokio::test] + async fn claim_is_cas_and_adopts_its_own_committed_assignment() { + let (driver, requests, sandbox, candidate) = claim_fixture(|object| { + let mut assigned = object.clone(); + assigned["metadata"]["annotations"][LABEL_SANDBOX_ID] = "logical".into(); + vec![(200, object.clone()), status(409), (200, assigned)] + }) + .await; + driver.claim_warm_pair(&sandbox, &candidate).await.unwrap(); + let requests = requests.lock().unwrap(); + assert_eq!(requests.len(), 3); + let patch = &requests[1].2["metadata"]; + assert_eq!(patch["resourceVersion"], "17"); + assert_eq!(patch["annotations"][LABEL_SANDBOX_ID], "logical"); + assert!(patch["labels"][POOL].is_null()); + assert!(patch["labels"][STATE].is_null()); + assert!( + patch["annotations"][PAIR].is_null(), + "physical identity must not be overwritten" + ); + } + + #[tokio::test] + async fn claim_rejects_another_owner_and_replaced_uid_without_mutation() { + for replaced in [false, true] { + let (driver, requests, sandbox, candidate) = claim_fixture(|object| { + let mut object = object.clone(); + if replaced { + object["metadata"]["uid"] = "replacement".into(); + } else { + object["metadata"]["annotations"][LABEL_SANDBOX_ID] = "other".into(); + } + vec![(200, object)] + }) + .await; + assert_eq!( + driver + .claim_warm_pair(&sandbox, &candidate) + .await + .unwrap_err() + .code(), + tonic::Code::Aborted + ); + assert_eq!(requests.lock().unwrap().len(), 1); + } + } + + #[tokio::test] + async fn retirement_winning_the_cas_fences_an_in_flight_claim() { + let (driver, requests, sandbox, candidate) = claim_fixture(|object| { + let mut retiring = object.clone(); + retiring["metadata"]["resourceVersion"] = "18".into(); + retiring["metadata"]["labels"][STATE] = "retiring".into(); + vec![(200, object.clone()), status(409), (200, retiring)] + }) + .await; + assert_eq!( + driver + .claim_warm_pair(&sandbox, &candidate) + .await + .unwrap_err() + .code(), + tonic::Code::Aborted + ); + let requests = requests.lock().unwrap(); + assert_eq!(requests.len(), 3); + assert_eq!(requests[1].0, "PATCH"); + assert_eq!(requests[2].0, "GET"); + } + + #[tokio::test] + async fn rejected_candidate_cancellation_cannot_delete_a_concurrent_successful_claim() { + let (driver, requests, sandbox, candidate) = claim_fixture(|object| { + let mut assigned = object.clone(); + assigned["metadata"]["annotations"][LABEL_SANDBOX_ID] = "logical".into(); + vec![(200, object.clone()), status(409), (200, assigned)] + }) + .await; + assert_eq!( + driver + .cancel_warm_pair(&sandbox.id, &candidate, true) + .await + .unwrap_err() + .code(), + tonic::Code::FailedPrecondition + ); + assert_eq!(requests.lock().unwrap().len(), 3); + assert!(requests.lock().unwrap().iter().all(|r| r.0 != "DELETE")); + } + + #[tokio::test] + async fn deletion_fences_an_unassigned_candidate_before_deleting_exact_uid() { + let (driver, requests, sandbox, candidate) = claim_fixture(|object| { + let mut retiring = object.clone(); + retiring["metadata"]["resourceVersion"] = "18".into(); + retiring["metadata"]["labels"][STATE] = "retiring".into(); + vec![ + (200, object.clone()), + (200, retiring.clone()), + (200, retiring), + ] + }) + .await; + driver + .cancel_warm_pair(&sandbox.id, &candidate, false) + .await + .unwrap(); + let requests = requests.lock().unwrap(); + assert_eq!(requests[1].2["metadata"]["labels"][STATE], "retiring"); + assert_eq!(requests[2].2["preconditions"]["uid"], "pair-uid"); + assert_eq!(requests[2].2["preconditions"]["resourceVersion"], "18"); + } + + #[tokio::test] + async fn pool_shape_allows_late_command_but_rejects_image_and_environment_changes() { + let (driver, _, mut sandbox, _) = claim_fixture(|_| vec![]).await; + let target = driver + .pool_targets + .read() + .await + .as_ref() + .unwrap() + .request + .pools[0] + .clone(); + sandbox.spec.as_mut().unwrap().command = vec!["custom-command".into()]; + assert!(driver.pool_shape_matches(&target, &sandbox)); + sandbox + .spec + .as_mut() + .unwrap() + .environment + .insert("CHANGED".into(), "1".into()); + assert!(!driver.pool_shape_matches(&target, &sandbox)); + sandbox.spec.as_mut().unwrap().environment.clear(); + sandbox.spec.as_mut().unwrap().template = + Some(openshell_core::proto::compute::v1::DriverSandboxTemplate { + image: "different".into(), + ..Default::default() + }); + assert!(!driver.pool_shape_matches(&target, &sandbox)); + } + + #[tokio::test] + async fn disabled_pool_discovers_and_retires_inventory_without_logical_ids() { + let object = serde_json::json!({ + "apiVersion": "agents.x-k8s.io/v1alpha1", "kind": "Sandbox", + "metadata": { + "name": "warm-example", "namespace": "openshell", "uid": "pair-uid", + "resourceVersion": "17", + "labels": { + LABEL_MANAGED_BY: LABEL_MANAGED_BY_VALUE, + LABEL_GATEWAY_ID: "openshell", POOL: "template", STATE: "preparing" + } + } + }); + let mut retiring = object.clone(); + retiring["metadata"]["resourceVersion"] = serde_json::json!("18"); + retiring["metadata"]["labels"][STATE] = serde_json::json!("retiring"); + let (driver, requests) = api_driver(vec![ + ( + 200, + serde_json::json!({"apiVersion":"agents.x-k8s.io/v1alpha1", "kind":"SandboxList", "metadata":{}, "items":[object]}), + ), + (200, retiring.clone()), + (200, retiring), + ]); + driver + .sandbox_api_version + .set(SANDBOX_VERSION_V1ALPHA1) + .unwrap(); + driver + .reconcile_pools(&SyncWarmPoolsRequest::default()) + .await + .unwrap(); + let requests = requests.lock().unwrap(); + assert_eq!(requests.len(), 3); + let (method, uri, _) = &requests[0]; + assert_eq!(method, "GET"); + assert!(uri.contains("labelSelector=")); + assert!(uri.contains("managed-by")); + assert!(uri.contains("gateway-id")); + assert!(uri.contains("warm-pool")); + assert!( + !uri.contains("sandbox-id"), + "unassigned pairs have no logical ID label" + ); + assert_eq!(requests[1].0, "PATCH"); + assert_eq!(requests[2].0, "DELETE"); + assert_eq!(requests[2].2["preconditions"]["uid"], "pair-uid"); + assert_eq!(requests[2].2["preconditions"]["resourceVersion"], "18"); + } + + #[tokio::test] + async fn retirement_failure_does_not_block_remaining_inventory() { + let object = |name: &str, uid: &str| { + serde_json::json!({ + "apiVersion": "agents.x-k8s.io/v1alpha1", "kind": "Sandbox", + "metadata": { + "name": name, "namespace": "openshell", "uid": uid, + "resourceVersion": "17", + "labels": { + LABEL_MANAGED_BY: LABEL_MANAGED_BY_VALUE, + LABEL_GATEWAY_ID: "openshell", POOL: "template", STATE: "preparing" + } + } + }) + }; + let first = object("warm-first", "first-uid"); + let second = object("warm-second", "second-uid"); + let mut retiring = second.clone(); + retiring["metadata"]["resourceVersion"] = serde_json::json!("18"); + retiring["metadata"]["labels"][STATE] = serde_json::json!("retiring"); + let (driver, requests) = api_driver(vec![ + ( + 200, + serde_json::json!({ + "apiVersion":"agents.x-k8s.io/v1alpha1", "kind":"SandboxList", + "metadata":{}, "items":[first, second] + }), + ), + status(500), + (200, retiring.clone()), + (200, retiring), + ]); + driver + .sandbox_api_version + .set(SANDBOX_VERSION_V1ALPHA1) + .unwrap(); + + driver + .reconcile_pools(&SyncWarmPoolsRequest::default()) + .await + .expect("one failed retirement must not abort reconciliation"); + + let requests = requests.lock().unwrap(); + assert_eq!(requests.len(), 4); + assert!(requests[1].1.contains("warm-first")); + assert!(requests[2].1.contains("warm-second")); + assert_eq!(requests[3].0, "DELETE"); + assert!(requests[3].1.contains("warm-second")); + } + + #[tokio::test] + async fn reconciliation_rotates_target_priority_between_passes() { + let driver = KubernetesComputeDriver::new_for_test(KubernetesComputeConfig::default()); + let request = SyncWarmPoolsRequest { + pools: ["a", "b", "c"] + .into_iter() + .map(|template_id| WarmPoolTarget { + template_id: template_id.to_string(), + workspace: "default".into(), + ..Default::default() + }) + .collect(), + ..Default::default() + }; + let order = || { + driver + .ordered_pool_targets(&request) + .into_iter() + .map(|target| target.template_id.as_str()) + .collect::>() + }; + + assert_eq!(order(), ["a", "b", "c"]); + assert_eq!(order(), ["b", "c", "a"]); + assert_eq!(order(), ["c", "a", "b"]); + } + + #[tokio::test] + async fn target_creation_failure_does_not_block_later_target() { + use openshell_core::proto::compute::v1::{ + DriverSandboxSpec, GpuResourceRequirements, ResourceRequirements, + }; + + let mut config = KubernetesComputeConfig::default(); + config.warm_pool.enabled = true; + config.warm_pool.max_pairs = 2; + let gateway_id = config.gateway_id.clone(); + let (mut driver, requests) = api_driver(vec![ + ( + 200, + serde_json::json!({ + "apiVersion":"agents.x-k8s.io/v1alpha1", "kind":"SandboxList", + "metadata":{}, "items":[] + }), + ), + ( + 200, + serde_json::json!({ + "apiVersion":"v1", "kind":"Namespace", + "metadata":{"name":"openshell", "uid":"namespace-uid"} + }), + ), + status(500), + ]); + driver.config = config; + driver + .sandbox_api_version + .set(SANDBOX_VERSION_V1ALPHA1) + .unwrap(); + let invalid = WarmPoolTarget { + template_id: "a-invalid".into(), + workspace: "default".into(), + target: 1, + spec: Some(DriverSandboxSpec { + resource_requirements: Some(ResourceRequirements { + gpu: Some(GpuResourceRequirements { count: Some(0) }), + }), + ..Default::default() + }), + ..Default::default() + }; + let healthy = WarmPoolTarget { + template_id: "b-healthy".into(), + workspace: "default".into(), + target: 1, + spec: Some(DriverSandboxSpec::default()), + ..Default::default() + }; + + driver + .reconcile_pools(&SyncWarmPoolsRequest { + pools: vec![invalid, healthy], + gateway_id, + verification_keys: b"[]".to_vec(), + }) + .await + .expect("target failures must remain local to their target"); + + let requests = requests.lock().unwrap(); + assert_eq!(requests.len(), 3); + assert!(requests[1].1.contains("/api/v1/namespaces/openshell")); + assert!(requests[2].1.contains("networkpolicies")); + } + + #[tokio::test] + async fn election_respects_live_holder_and_cas_conflicts() { + let lease = serde_json::json!({"apiVersion":"coordination.k8s.io/v1","kind":"Lease","metadata":{"name":"lease","resourceVersion":"17"},"spec":{"holderIdentity":"other","leaseDurationSeconds":30,"renewTime":k8s_openapi::chrono::Utc::now()}}); + let (driver, requests) = api_driver(vec![(200, lease.clone())]); + assert!(!driver.renew_pool_lease("me").await.unwrap()); + assert_eq!(requests.lock().unwrap().len(), 1); + let mut expired = lease; + expired["spec"]["renewTime"] = serde_json::json!("2000-01-01T00:00:00Z"); + let (driver, requests) = api_driver(vec![(200, expired), status(409)]); + assert!(!driver.renew_pool_lease("me").await.unwrap()); + let requests = requests.lock().unwrap(); + assert_eq!(requests[1].0, "PUT"); + assert_eq!(requests[1].2["metadata"]["resourceVersion"], "17"); + } + + #[tokio::test] + async fn retirement_conflict_never_deletes_a_claimed_pair() { + let (driver, requests) = api_driver(vec![status(409)]); + driver + .sandbox_api_version + .set(SANDBOX_VERSION_V1ALPHA1) + .unwrap(); + let resource = ApiResource::from_gvk(&GroupVersionKind::gvk( + SANDBOX_GROUP, + SANDBOX_VERSION_V1ALPHA1, + SANDBOX_KIND, + )); + let mut object = DynamicObject::new("warm-example", &resource); + object.metadata.resource_version = Some("17".into()); + object.metadata.uid = Some("pair-uid".into()); + driver.retire_pair(&object).await.unwrap(); + let requests = requests.lock().unwrap(); + assert_eq!(requests.len(), 1); + assert_eq!(requests[0].0, "PATCH"); + assert_eq!(requests[0].2["metadata"]["resourceVersion"], "17"); + } + + #[tokio::test] + async fn preparation_journal_reuses_existing_keys_and_rejects_new_owner() { + let mut secret = Secret { + metadata: ObjectMeta { + name: Some("journal".into()), + owner_references: Some(vec![OwnerReference { + api_version: "v1".into(), + kind: "Pod".into(), + name: "pod".into(), + uid: "original".into(), + ..Default::default() + }]), + ..Default::default() + }, + data: Some(BTreeMap::from([( + "key".into(), + k8s_openapi::ByteString(b"original material".to_vec()), + )])), + ..Default::default() + }; + let (driver, requests) = api_driver(vec![ + (200, serde_json::to_value(&secret).unwrap()), + (200, serde_json::to_value(&secret).unwrap()), + ]); + let api = Api::::namespaced(driver.client, "default"); + secret.data = None; // retry generated different material; retain committed journal + let reused = create_owned(&api, &secret).await.unwrap(); + assert_eq!(reused.data.unwrap()["key"].0, b"original material"); + secret.metadata.owner_references.as_mut().unwrap()[0].uid = "replacement".into(); + assert!(create_owned(&api, &secret).await.is_err()); + assert!(requests.lock().unwrap().iter().all(|r| r.0 == "GET")); + } + + #[tokio::test] + async fn preparation_revision_ignores_capacity_but_tracks_shape_and_trust() { + let driver = KubernetesComputeDriver::new_for_test(KubernetesComputeConfig::default()); + let mut target = WarmPoolTarget { + template_id: "template".into(), + template_name: "example-template".into(), + workspace: "default".into(), + target: 1, + spec: Some(DriverSandboxSpec::default()), + }; + let mut request = SyncWarmPoolsRequest { + gateway_id: "gateway".into(), + verification_keys: b"public-key-a".to_vec(), + ..Default::default() + }; + let revision = driver.pool_fingerprint(&target, &request).unwrap(); + target.target = 20; + target.template_name = "renamed-for-display".into(); + assert_eq!( + revision, + driver.pool_fingerprint(&target, &request).unwrap() + ); + target + .spec + .as_mut() + .unwrap() + .environment + .insert("MODEL".into(), "other".into()); + assert_ne!( + revision, + driver.pool_fingerprint(&target, &request).unwrap() + ); + target.spec.as_mut().unwrap().environment.clear(); + request.verification_keys = b"public-key-b".to_vec(); + assert_ne!( + revision, + driver.pool_fingerprint(&target, &request).unwrap() + ); + } + + #[test] + fn inventory_never_has_a_logical_sandbox_identity() { + let resource = ApiResource::from_gvk(&GroupVersionKind::gvk( + SANDBOX_GROUP, + SANDBOX_VERSION_V1ALPHA1, + SANDBOX_KIND, + )); + let mut object = DynamicObject::new("warm-example", &resource); + object.metadata.labels = Some(BTreeMap::from([(POOL.into(), "template".into())])); + assert!(is_pool_pair(&object)); + assert!(sandbox_id_from_object(&object).is_err()); + object.metadata.annotations = Some(BTreeMap::from([( + LABEL_SANDBOX_ID.into(), + "assigned".into(), + )])); + assert!(!is_pool_pair(&object)); + assert_eq!(sandbox_id_from_object(&object).unwrap(), "assigned"); + } +} diff --git a/crates/openshell-driver-mxc/src/grpc.rs b/crates/openshell-driver-mxc/src/grpc.rs index 7ddff4dfff..5709cdbcb5 100644 --- a/crates/openshell-driver-mxc/src/grpc.rs +++ b/crates/openshell-driver-mxc/src/grpc.rs @@ -34,6 +34,23 @@ impl ComputeDriverService { #[tonic::async_trait] impl ComputeDriver for ComputeDriverService { + async fn select_warm_pair( + &self, + request: Request, + ) -> Result, Status> { + let _ = &request; + Err(Status::unimplemented( + "warm pair allocation is not supported", + )) + } + + async fn sync_warm_pools( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("warm pools are not supported")) + } + async fn get_capabilities( &self, request: Request, diff --git a/crates/openshell-driver-podman/src/grpc.rs b/crates/openshell-driver-podman/src/grpc.rs index a25b6b7e4d..360bcfc7ad 100644 --- a/crates/openshell-driver-podman/src/grpc.rs +++ b/crates/openshell-driver-podman/src/grpc.rs @@ -50,6 +50,23 @@ impl ComputeDriverService { #[tonic::async_trait] impl ComputeDriver for ComputeDriverService { + async fn select_warm_pair( + &self, + request: Request, + ) -> Result, Status> { + let _ = &request; + Err(Status::unimplemented( + "warm pair allocation is not supported", + )) + } + + async fn sync_warm_pools( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("warm pools are not supported")) + } + async fn authenticate_sandbox( &self, _request: Request, @@ -663,6 +680,7 @@ mod tests { Request::new(DeleteSandboxRequest { sandbox_id: String::new(), name: "demo".to_string(), + ..Default::default() }), ) .await @@ -694,6 +712,7 @@ mod tests { Request::new(DeleteSandboxRequest { sandbox_id: sandbox_id.to_string(), name: "demo".to_string(), + ..Default::default() }), ) .await diff --git a/crates/openshell-driver-vm/src/driver.rs b/crates/openshell-driver-vm/src/driver.rs index fc337ea7ce..2e22b7d208 100644 --- a/crates/openshell-driver-vm/src/driver.rs +++ b/crates/openshell-driver-vm/src/driver.rs @@ -4261,6 +4261,23 @@ fn configure_main_exit_marker(command: &mut Command, state_dir: &Path) { #[tonic::async_trait] impl ComputeDriver for VmDriver { + async fn select_warm_pair( + &self, + request: Request, + ) -> Result, Status> { + let _ = &request; + Err(Status::unimplemented( + "warm pair allocation is not supported", + )) + } + + async fn sync_warm_pools( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("warm pools are not supported")) + } + async fn authenticate_sandbox( &self, _request: Request, @@ -7234,6 +7251,7 @@ mod tests { client .create_sandbox(request_with_traceparent(CreateSandboxRequest { sandbox: None, + ..Default::default() })) .await .is_err() @@ -7264,6 +7282,7 @@ mod tests { .delete_sandbox(request_with_traceparent(DeleteSandboxRequest { sandbox_id: String::new(), name: String::new(), + ..Default::default() })) .await .unwrap(); @@ -7341,6 +7360,7 @@ mod tests { }; let request = request_with_traceparent(CreateSandboxRequest { sandbox: Some(sandbox), + ..Default::default() }); let mut client = traced_driver_client(driver.clone()).await; diff --git a/crates/openshell-otel/src/grpc.rs b/crates/openshell-otel/src/grpc.rs index e22af83968..1d6035c66e 100644 --- a/crates/openshell-otel/src/grpc.rs +++ b/crates/openshell-otel/src/grpc.rs @@ -86,6 +86,14 @@ impl ComputeDriverRpc { pub mod rpc { use super::ComputeDriverRpc; + pub const SELECT_WARM_PAIR: ComputeDriverRpc = ComputeDriverRpc::new( + "SelectWarmPair", + "openshell.compute.v1.ComputeDriver/SelectWarmPair", + ); + pub const SYNC_WARM_POOLS: ComputeDriverRpc = ComputeDriverRpc::new( + "SyncWarmPools", + "openshell.compute.v1.ComputeDriver/SyncWarmPools", + ); pub const AUTHENTICATE_SANDBOX: ComputeDriverRpc = ComputeDriverRpc::new( "AuthenticateSandbox", "openshell.compute.v1.ComputeDriver/AuthenticateSandbox", @@ -186,6 +194,8 @@ impl MakeSpan for ComputeDriverRpcSpan { /// Maps the generated compute-driver RPC schema to low-cardinality span names. pub fn compute_driver_rpc_operation(path: &str) -> Option { match path.rsplit('/').next() { + Some("SelectWarmPair") => Some(rpc::SELECT_WARM_PAIR), + Some("SyncWarmPools") => Some(rpc::SYNC_WARM_POOLS), Some("AuthenticateSandbox") => Some(rpc::AUTHENTICATE_SANDBOX), Some("GetCapabilities") => Some(rpc::GET_CAPABILITIES), Some("ValidateSandboxCreate") => Some(rpc::VALIDATE_SANDBOX_CREATE), @@ -308,6 +318,8 @@ mod tests { #[test] fn compute_driver_rpc_names_are_explicitly_mapped_and_schema_bounded() { for rpc in [ + rpc::SELECT_WARM_PAIR, + rpc::SYNC_WARM_POOLS, rpc::AUTHENTICATE_SANDBOX, rpc::GET_CAPABILITIES, rpc::VALIDATE_SANDBOX_CREATE, diff --git a/crates/openshell-sandbox-backend/src/boundary_protocol.rs b/crates/openshell-sandbox-backend/src/boundary_protocol.rs index 05542fb2bc..1f8f5a3134 100644 --- a/crates/openshell-sandbox-backend/src/boundary_protocol.rs +++ b/crates/openshell-sandbox-backend/src/boundary_protocol.rs @@ -323,7 +323,8 @@ impl SandboxRuntimeDescriptor { #[derive(Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct BoundaryConfig { - /// Stable identity expected in every authenticated request. + /// Logical sandbox identity. Empty for a prepared Kubernetes pair whose + /// first signed, resource-bound credential establishes assignment. pub boundary_id: String, /// Immutable driver-owned workload generation. pub generation: String, diff --git a/crates/openshell-sandbox-backend/src/runtime.rs b/crates/openshell-sandbox-backend/src/runtime.rs index 1b56d6df9d..a4d4b120c7 100644 --- a/crates/openshell-sandbox-backend/src/runtime.rs +++ b/crates/openshell-sandbox-backend/src/runtime.rs @@ -52,6 +52,9 @@ const ATTACH_REQUEST_TIMEOUT: Duration = Duration::from_mins(5); /// callers retry whole calls above this; past boot, exhausting this window /// means the remote boundary (or its launcher) is gone rather than still starting. const CONNECT_RETRY_TIMEOUT: Duration = Duration::from_secs(30); +/// Retry fresh connections promptly when an early boot connection is blackholed. +/// This bounds both transport establishment and the TLS handshake. +const CONNECT_ATTEMPT_TIMEOUT: Duration = Duration::from_secs(2); fn begin_recovery_window( deadline: &mut Option, @@ -1632,12 +1635,43 @@ impl BoundaryClient { async fn connect_boundary_with_retry( runtime_descriptor: &SandboxRuntimeDescriptor, ) -> Result { - let deadline = tokio::time::Instant::now() + CONNECT_RETRY_TIMEOUT; + connect_boundary_with_timeouts( + runtime_descriptor, + CONNECT_RETRY_TIMEOUT, + CONNECT_ATTEMPT_TIMEOUT, + ) + .await +} + +async fn connect_boundary_with_timeouts( + runtime_descriptor: &SandboxRuntimeDescriptor, + retry_timeout: Duration, + attempt_timeout: Duration, +) -> Result { + let deadline = tokio::time::Instant::now() + retry_timeout; loop { - match connect_boundary_once(runtime_descriptor).await { + let attempt_deadline = (tokio::time::Instant::now() + attempt_timeout).min(deadline); + let result = + tokio::time::timeout_at(attempt_deadline, connect_boundary_once(runtime_descriptor)) + .await + .unwrap_or_else(|_| { + Err(BackendError::Unavailable( + "boundary transport connection or TLS handshake timed out".to_string(), + )) + }); + match result { Ok(stream) => return Ok(stream), Err(error) if tokio::time::Instant::now() >= deadline => return Err(error), - Err(_) => tokio::time::sleep(Duration::from_millis(25)).await, + Err(error) => { + tracing::debug!(%error, "boundary connection attempt failed; retrying"); + tokio::time::sleep_until( + (tokio::time::Instant::now() + Duration::from_millis(25)).min(deadline), + ) + .await; + if tokio::time::Instant::now() >= deadline { + return Err(error); + } + } } } } @@ -1931,6 +1965,57 @@ mod tests { } } + #[tokio::test] + async fn boundary_connect_retries_a_stalled_tls_handshake() { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let address = listener.local_addr().unwrap(); + let certificate = test_certificate(); + let descriptor = tls_runtime_descriptor(address, certificate.client_tls); + let server = tokio::spawn(async move { + // Keep the first socket open without answering its TLS handshake. + let (stalled, _) = listener.accept().await.unwrap(); + let (stream, _) = listener.accept().await.unwrap(); + let accepted = tokio_rustls::TlsAcceptor::from(certificate.server_config) + .accept(stream) + .await + .unwrap(); + (stalled, accepted) + }); + let connected = tokio::time::timeout( + Duration::from_secs(5), + connect_boundary_with_retry(&descriptor), + ) + .await + .expect("a stalled handshake must not delay the next attempt indefinitely") + .expect("the next TLS connection should succeed"); + let _server_streams = server.await.unwrap(); + drop(connected); + } + + #[tokio::test] + async fn boundary_connect_deadline_caps_an_inflight_attempt() { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let descriptor = tls_runtime_descriptor( + listener.local_addr().unwrap(), + test_certificate().client_tls, + ); + // The listening socket accepts TCP in the kernel but never answers TLS. + // The overall deadline must cancel an attempt with a longer timeout. + let result = tokio::time::timeout( + Duration::from_secs(1), + connect_boundary_with_timeouts( + &descriptor, + Duration::from_millis(100), + Duration::from_secs(5), + ), + ) + .await + .expect("the overall retry deadline must interrupt a pending attempt"); + assert!( + matches!(result, Err(BackendError::Unavailable(message)) if message.contains("timed out")) + ); + } + #[tokio::test] async fn boundary_tcp_connections_enable_keepalive() { let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); diff --git a/crates/openshell-sandbox-backend/src/sandbox_auth.rs b/crates/openshell-sandbox-backend/src/sandbox_auth.rs index 8cd3878283..3e69d0f841 100644 --- a/crates/openshell-sandbox-backend/src/sandbox_auth.rs +++ b/crates/openshell-sandbox-backend/src/sandbox_auth.rs @@ -54,7 +54,8 @@ impl SandboxProtocolPrincipal { /// Validates Sandbox Protocol metadata without depending on its byte transport. pub struct SandboxProtocolAuthenticator { verifier: SessionJwtVerifier, - expected_sandbox_id: SandboxId, + expected_sandbox_id: Mutex>, + resource_binding: std::collections::BTreeMap, expected_runtime_generation: SandboxGenerationId, expected_auth_epoch: CredentialEpoch, } @@ -84,7 +85,29 @@ impl SandboxProtocolAuthenticator { ) -> Self { Self { verifier, - expected_sandbox_id, + expected_sandbox_id: Mutex::new(Some(expected_sandbox_id)), + resource_binding: std::collections::BTreeMap::new(), + expected_runtime_generation, + expected_auth_epoch, + } + } + + /// Wait for a gateway-signed assignment to the protected physical runtime. + /// Transport authentication alone must never select a logical sandbox. + pub fn unassigned( + verifier: SessionJwtVerifier, + resource_binding: std::collections::BTreeMap, + expected_runtime_generation: SandboxGenerationId, + expected_auth_epoch: CredentialEpoch, + ) -> Self { + assert!( + !resource_binding.is_empty(), + "assignment requires a runtime binding" + ); + Self { + verifier, + expected_sandbox_id: Mutex::new(None), + resource_binding, expected_runtime_generation, expected_auth_epoch, } @@ -108,15 +131,26 @@ impl SandboxProtocolAuthenticator { .filter(|token| !token.is_empty() && !token.chars().any(char::is_whitespace)) .ok_or(SandboxAuthError::InvalidBearer)?; let session = self.verifier.verify(token)?; - if session.sandbox_id != self.expected_sandbox_id { - return Err(SandboxAuthError::WrongSandbox); - } if session.runtime_generation != self.expected_runtime_generation { return Err(SandboxAuthError::WrongRuntimeGeneration); } if session.auth_epoch != self.expected_auth_epoch { return Err(SandboxAuthError::StaleCredentialEpoch); } + let mut assigned = self + .expected_sandbox_id + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(expected) = assigned.as_ref() { + if &session.sandbox_id != expected { + return Err(SandboxAuthError::WrongSandbox); + } + } else { + if session.resource_binding != self.resource_binding { + return Err(SandboxAuthError::WrongSandbox); + } + *assigned = Some(session.sandbox_id.clone()); + } Ok(SandboxProtocolPrincipal { connection_id, session, @@ -417,6 +451,95 @@ mod tests { ) } + #[test] + fn late_assignment_requires_signed_binding_and_is_permanent() { + use std::collections::BTreeMap; + let key = KeyPair::generate_for(&PKCS_ED25519).unwrap(); + let clock: Arc = Arc::new(FixedClock); + let issuer = SessionJwtIssuer::from_ed25519_pem( + key.serialize_pem().as_bytes(), + "key", + "gateway", + DEFAULT_SESSION_TOKEN_TTL, + clock.clone(), + ) + .unwrap(); + let verifier = SessionJwtVerifier::new( + "gateway", + SessionTokenProfile::Sandbox, + [SessionVerificationKey { + key_id: "key".into(), + public_key_pem: key.public_key_pem().into_bytes(), + }], + clock, + ) + .unwrap(); + let binding = BTreeMap::from([ + ( + "kubernetes.sandbox_resource_uid".into(), + "sandbox-uid".into(), + ), + ("kubernetes.workload_pod_uid".into(), "pod-uid".into()), + ]); + let identity = SandboxRuntimeIdentity { + sandbox_id: SandboxId::parse("logical-a").unwrap(), + runtime_generation: SandboxGenerationId::parse("generation-a").unwrap(), + auth_epoch: CredentialEpoch::new(1).unwrap(), + }; + let auth = SandboxProtocolAuthenticator::unassigned( + verifier, + binding.clone(), + identity.runtime_generation.clone(), + identity.auth_epoch, + ); + let authenticate = |token: &openshell_core::jwt::MintedSessionToken| { + auth.authenticate( + SandboxConnectionId::new(), + &metadata(token.token.expose_secret()), + ) + }; + // Even a valid gateway-issued operational token cannot win first assignment. + assert!(authenticate(&issuer.mint_pair(&identity).unwrap().sandbox).is_err()); + for field in binding.keys() { + let mut wrong = binding.clone(); + wrong.insert(field.clone(), "replacement-uid".into()); + assert!( + authenticate(&issuer.mint_bound_sandbox_token(&identity, wrong).unwrap()).is_err() + ); + } + let mut wrong = identity.clone(); + wrong.runtime_generation = SandboxGenerationId::parse("old-generation").unwrap(); + assert!( + authenticate( + &issuer + .mint_bound_sandbox_token(&wrong, binding.clone()) + .unwrap() + ) + .is_err() + ); + wrong = identity.clone(); + wrong.auth_epoch = CredentialEpoch::new(2).unwrap(); + assert!( + authenticate( + &issuer + .mint_bound_sandbox_token(&wrong, binding.clone()) + .unwrap() + ) + .is_err() + ); + let token = issuer + .mint_bound_sandbox_token(&identity, binding.clone()) + .unwrap(); + let first = authenticate(&token).unwrap(); + assert_eq!(first.session().sandbox_id, identity.sandbox_id); + assert!(authenticate(&token).is_ok()); + // Ordinary refreshed tokens work only after the identity was assigned. + assert!(authenticate(&issuer.mint_pair(&identity).unwrap().sandbox).is_ok()); + wrong = identity; + wrong.sandbox_id = SandboxId::parse("logical-b").unwrap(); + assert!(authenticate(&issuer.mint_bound_sandbox_token(&wrong, binding).unwrap()).is_err()); + } + fn registry_for(_principal: &SandboxProtocolPrincipal) -> SandboxConnectionRegistry { SandboxConnectionRegistry::new( openshell_core::SandboxSessionId::new(), diff --git a/crates/openshell-sandbox/src/boundary_server.rs b/crates/openshell-sandbox/src/boundary_server.rs index 5452ea6efa..d76f7d1742 100644 --- a/crates/openshell-sandbox/src/boundary_server.rs +++ b/crates/openshell-sandbox/src/boundary_server.rs @@ -290,8 +290,20 @@ mod linux { } fn validate_config(config: &BoundaryConfig) -> Result<(), String> { - if config.boundary_id.is_empty() { - return Err("boundary ID must not be empty".to_string()); + if config.boundary_id.is_empty() + && [ + "kubernetes.sandbox_resource_uid", + "kubernetes.workload_pod_uid", + ] + .iter() + .any(|key| { + config + .resource_claims + .get(*key) + .is_none_or(String::is_empty) + }) + { + return Err("unassigned boundary requires Sandbox and workload Pod UIDs".to_string()); } if config.generation.is_empty() { return Err("boundary generation must not be empty".to_string()); @@ -479,6 +491,16 @@ mod linux { fn serve(config: &BoundaryListenerConfig, runtime: Arc) -> Result<(), String> { let listener = ControlListener::bind(config) .map_err(|error| format!("bind boundary control listener: {error}"))?; + if matches!(config, BoundaryListenerConfig::TlsTcp { .. }) + && Path::new("/.openshell/state").is_dir() + { + // The probe also verifies the current process start time, so a stale + // marker in the runtime volume cannot make a replacement ready. + let stat = std::fs::read_to_string("/proc/self/stat") + .map_err(|error| format!("read boundary process identity: {error}"))?; + std::fs::write("/.openshell/state/boundary-ready", stat) + .map_err(|error| format!("publish boundary listener readiness: {error}"))?; + } let active_connections = Arc::new(AtomicUsize::new(0)); let pending_handshakes = Arc::new(tokio::sync::Semaphore::new(MAX_PENDING_HANDSHAKES)); tracing::info!(?config, "Boundary control listener ready"); @@ -1476,8 +1498,6 @@ mod linux { workload_launcher: openshell_isolation_interface::linux::workload_launcher::WorkloadLauncher, qualification: crate::RuntimeQualification, ) -> Result { - let sandbox_id = SandboxId::parse(config.boundary_id.clone()) - .map_err(|error| format!("validate sandbox ID: {error}"))?; let runtime_generation = openshell_core::sandbox_generation::SandboxGenerationId::parse( config.generation.clone(), @@ -1496,13 +1516,24 @@ mod linux { Arc::new(SystemJwtClock), ) .map_err(|error| format!("configure Sandbox Protocol JWT verifier: {error}"))?; - Ok(Self { - authenticator: SandboxProtocolAuthenticator::new( + let authenticator = if config.boundary_id.is_empty() { + SandboxProtocolAuthenticator::unassigned( verifier, - sandbox_id, + config.resource_claims.clone(), runtime_generation, config.auth_epoch, - ), + ) + } else { + SandboxProtocolAuthenticator::new( + verifier, + SandboxId::parse(config.boundary_id.clone()) + .map_err(|error| format!("validate sandbox ID: {error}"))?, + runtime_generation, + config.auth_epoch, + ) + }; + Ok(Self { + authenticator, connections: SandboxConnectionRegistry::new( config.session_id, config.session_rotation, diff --git a/crates/openshell-sandbox/src/main.rs b/crates/openshell-sandbox/src/main.rs index 24fa65ab16..0965bf8e85 100644 --- a/crates/openshell-sandbox/src/main.rs +++ b/crates/openshell-sandbox/src/main.rs @@ -1856,8 +1856,31 @@ fn run_boundary(_bootstrap: &Path, _log_level: &str) -> Result<()> { Err(miette::miette!("openshell-sandbox requires Linux")) } +fn boundary_health() -> Result<()> { + let saved = std::fs::read_to_string("/.openshell/state/boundary-ready").into_diagnostic()?; + let pid = saved + .split_whitespace() + .next() + .ok_or_else(|| miette::miette!("missing boundary PID"))?; + let pid: u32 = pid.parse().into_diagnostic()?; + let current = std::fs::read_to_string(format!("/proc/{pid}/stat")).into_diagnostic()?; + // comm may contain spaces: fields after the final ')' start at field 3. + let start = |stat: &str| { + stat.rsplit_once(')') + .and_then(|(_, rest)| rest.split_whitespace().nth(19)) + .map(str::to_owned) + }; + if start(&saved).is_none() || start(&saved) != start(¤t) { + return Err(miette::miette!("boundary listener is not ready")); + } + Ok(()) +} + fn main() -> Result<()> { let raw_args = std::env::args().collect::>(); + if raw_args.get(1).map(String::as_str) == Some("boundary-health") { + return boundary_health(); + } if raw_args.get(1).map(String::as_str) == Some(COPY_SELF_SUBCOMMAND) { let dest = raw_args.get(2).ok_or_else(|| { miette::miette!("usage: openshell-sandbox {COPY_SELF_SUBCOMMAND} ") diff --git a/crates/openshell-sdk/tests/client_mock.rs b/crates/openshell-sdk/tests/client_mock.rs index 75f3035cc2..9485d86df8 100644 --- a/crates/openshell-sdk/tests/client_mock.rs +++ b/crates/openshell-sdk/tests/client_mock.rs @@ -864,6 +864,13 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("unused")) } + async fn register_supervisor( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("not implemented in test")) + } + async fn issue_sandbox_token( &self, _: tonic::Request, diff --git a/crates/openshell-server/migrations/postgres/009_allocation_claims.sql b/crates/openshell-server/migrations/postgres/009_allocation_claims.sql new file mode 100644 index 0000000000..146196a1c0 --- /dev/null +++ b/crates/openshell-server/migrations/postgres/009_allocation_claims.sql @@ -0,0 +1,7 @@ +CREATE TABLE allocation_claims ( + target TEXT PRIMARY KEY, + sandbox_id TEXT NOT NULL REFERENCES objects(id) ON DELETE CASCADE, + attempt_id TEXT NOT NULL, + runtime_generation TEXT NOT NULL +); +CREATE INDEX allocation_claims_sandbox_idx ON allocation_claims (sandbox_id); diff --git a/crates/openshell-server/migrations/sqlite/009_allocation_claims.sql b/crates/openshell-server/migrations/sqlite/009_allocation_claims.sql new file mode 100644 index 0000000000..146196a1c0 --- /dev/null +++ b/crates/openshell-server/migrations/sqlite/009_allocation_claims.sql @@ -0,0 +1,7 @@ +CREATE TABLE allocation_claims ( + target TEXT PRIMARY KEY, + sandbox_id TEXT NOT NULL REFERENCES objects(id) ON DELETE CASCADE, + attempt_id TEXT NOT NULL, + runtime_generation TEXT NOT NULL +); +CREATE INDEX allocation_claims_sandbox_idx ON allocation_claims (sandbox_id); diff --git a/crates/openshell-server/src/auth/compute_driver.rs b/crates/openshell-server/src/auth/compute_driver.rs index 17f1f98c23..1c5ddf523e 100644 --- a/crates/openshell-server/src/auth/compute_driver.rs +++ b/crates/openshell-server/src/auth/compute_driver.rs @@ -9,8 +9,10 @@ use crate::compute::ComputeRuntime; use async_trait::async_trait; use tonic::Status; -/// The only public gateway method on which driver-native credentials apply. +/// Legacy token exchange endpoint accepting driver-native credentials. pub const ISSUE_SANDBOX_TOKEN_PATH: &str = "/openshell.v1.OpenShell/IssueSandboxToken"; +/// Runtime assignment endpoint accepting driver-native credentials. +pub const REGISTER_SUPERVISOR_PATH: &str = "/openshell.v1.OpenShell/RegisterSupervisor"; #[derive(Clone, Debug)] pub struct ComputeDriverAuthenticator { @@ -30,7 +32,7 @@ impl Authenticator for ComputeDriverAuthenticator { headers: &http::HeaderMap, path: &str, ) -> Result, Status> { - if path != ISSUE_SANDBOX_TOKEN_PATH { + if path != ISSUE_SANDBOX_TOKEN_PATH && path != REGISTER_SUPERVISOR_PATH { return Ok(None); } @@ -42,6 +44,18 @@ impl Authenticator for ComputeDriverAuthenticator { return Ok(None); }; + if path == REGISTER_SUPERVISOR_PATH { + let response = self.compute.authenticate_supervisor(credential).await?; + return Ok(Some(Principal::Sandbox(SandboxPrincipal { + sandbox_id: response.sandbox_id, + source: SandboxIdentitySource::SupervisorRegistration { + registration: response.registration.ok_or_else(|| { + Status::permission_denied("missing registration identity") + })?, + }, + trust_domain: Some("openshell".to_string()), + }))); + } let sandbox_id = self.compute.authenticate_sandbox(credential).await?; if sandbox_id.is_empty() { return Err(Status::permission_denied( diff --git a/crates/openshell-server/src/auth/principal.rs b/crates/openshell-server/src/auth/principal.rs index 9567cc62d2..8722050c5b 100644 --- a/crates/openshell-server/src/auth/principal.rs +++ b/crates/openshell-server/src/auth/principal.rs @@ -64,6 +64,10 @@ pub struct SandboxPrincipal { #[derive(Debug, Clone)] #[allow(dead_code)] pub enum SandboxIdentitySource { + /// Driver-verified physical proxy identity, valid only on `RegisterSupervisor`. + SupervisorRegistration { + registration: openshell_core::proto::compute::v1::SupervisorRegistration, + }, /// Gateway-minted JWT validated against the gateway's signing key. /// Produced by [`super::sandbox_jwt::SandboxJwtAuthenticator`]. BootstrapJwt { issuer: String }, diff --git a/crates/openshell-server/src/auth/sandbox_jwt.rs b/crates/openshell-server/src/auth/sandbox_jwt.rs index 8593c01b33..e5dd75f32a 100644 --- a/crates/openshell-server/src/auth/sandbox_jwt.rs +++ b/crates/openshell-server/src/auth/sandbox_jwt.rs @@ -262,11 +262,51 @@ impl SandboxSessionJwtAuthority { }) } + pub(crate) fn preparation_trust(&self) -> Result<(String, Vec), Status> { + Ok(( + self.gateway_id.clone(), + serde_json::to_vec(&self.verification_keys) + .map_err(|_| Status::internal("encode preparation trust"))?, + )) + } + pub fn verify_gateway_token(&self, token: &str) -> Result { self.gateway_verifier .verify(token) .map_err(|error| Status::unauthenticated(format!("invalid gateway session: {error}"))) } + + /// Mint initial assignment without reviving a superseded credential lineage. + pub fn mint_registration( + &self, + sandbox_id: &str, + identity: &crate::auth::sandbox_session::PersistedSandboxIdentity, + session_id: SandboxSessionId, + resource_binding: std::collections::BTreeMap, + ) -> Result { + if identity.refresh_replay.is_some() { + return Err(Status::failed_precondition( + "runtime has already refreshed; use the cold-path restart lifecycle", + )); + } + let mut bundle = self.mint_persisted_launch(sandbox_id, identity)?.supervisor; + let token = self + .issuer + .mint_bound_sandbox_token( + &SandboxRuntimeIdentity { + sandbox_id: SandboxId::parse(sandbox_id) + .map_err(|_| Status::invalid_argument("invalid sandbox ID"))?, + runtime_generation: identity.runtime_generation.clone(), + auth_epoch: identity.auth_epoch, + }, + resource_binding, + ) + .map_err(|_| Status::internal("assignment signing failed"))?; + bundle.session_id = session_id; + bundle.sandbox_token = token.token; + bundle.sandbox_expires_at = token.expires_at; + Ok(bundle) + } } /// Authenticates launch-scoped supervisor tokens and checks their identity diff --git a/crates/openshell-server/src/auth/sandbox_session.rs b/crates/openshell-server/src/auth/sandbox_session.rs index 04f3b7a050..ed9221a61e 100644 --- a/crates/openshell-server/src/auth/sandbox_session.rs +++ b/crates/openshell-server/src/auth/sandbox_session.rs @@ -410,6 +410,7 @@ mod tests { fn principal(auth_epoch: u64, token_id: Uuid) -> AuthenticatedSandboxSession { AuthenticatedSandboxSession { + resource_binding: std::collections::BTreeMap::default(), sandbox_id: SandboxId::parse("sandbox-a").expect("sandbox ID"), runtime_generation: SandboxGenerationId::parse("generation-a") .expect("runtime generation"), diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index 55ffd1b8ba..21ef978d78 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -7,6 +7,7 @@ pub mod driver_config; pub mod lease; pub mod provisioning_deadline; pub mod rootfs_tar; +pub mod warm_pool; use crate::grpc::policy::SANDBOX_SETTINGS_OBJECT_TYPE; use crate::otel_tracing::TraceContextInterceptor; @@ -21,6 +22,7 @@ use crate::tracing_bus::TracingLogBus; use futures::{Stream, StreamExt}; #[cfg(unix)] use hyper_util::rt::TokioIo; +use openshell_core::ObjectWorkspace; use openshell_core::extension_protocol::{ ExtensionFamily, NegotiatedExtension, gateway_metadata, negotiate, }; @@ -41,7 +43,6 @@ use openshell_core::proto::{ SandboxTemplate, SandboxWorkloadTemplate, ServiceEndpoint, SshSession, }; use openshell_core::telemetry::TelemetryComputeDriver; -use openshell_core::{ObjectLabels, ObjectWorkspace}; use prost::Message; use std::collections::HashMap; use std::fmt; @@ -470,6 +471,23 @@ impl RemoteComputeDriver { #[tonic::async_trait] impl ComputeDriver for RemoteComputeDriver { + async fn select_warm_pair( + &self, + request: Request, + ) -> Result, Status> + { + let _ = &request; + self.client.clone().select_warm_pair(request).await + } + + async fn sync_warm_pools( + &self, + request: Request, + ) -> Result, Status> + { + self.client.clone().sync_warm_pools(request).await + } + type WatchSandboxesStream = DriverWatchStream; async fn get_capabilities( @@ -598,6 +616,8 @@ pub struct ComputeRuntime { supervisor_sessions: Arc, sync_lock: Arc>, lifecycle_gates: Arc, + /// Wake local registration waiters; durable driver state remains authoritative. + assignment_notifications: watch::Sender<()>, replica_id: String, /// Gateway-issued staging slots for rootfs tar archives. Shared across /// clones: `ServerState` holds `ComputeRuntime` by value, so a per-clone @@ -612,6 +632,10 @@ impl fmt::Debug for ComputeRuntime { } impl ComputeRuntime { + pub(crate) fn subscribe_assignments(&self) -> watch::Receiver<()> { + self.assignment_notifications.subscribe() + } + #[allow(clippy::too_many_arguments)] #[tracing::instrument( name = "driver.initialize", @@ -687,6 +711,7 @@ impl ComputeRuntime { supervisor_sessions, sync_lock: Arc::new(Mutex::new(())), lifecycle_gates: Arc::new(LifecycleGateRegistry::default()), + assignment_notifications: watch::channel(()).0, replica_id: lease::replica_id(), rootfs_tar_staging, }) @@ -776,6 +801,7 @@ impl ComputeRuntime { } let request = AuthenticateSandboxRequest { credential: credential.to_string(), + supervisor_registration: false, }; self.driver .call( @@ -787,6 +813,36 @@ impl ComputeRuntime { .map(|response| response.into_inner().sandbox_id) } + pub(crate) async fn authenticate_supervisor( + &self, + credential: &str, + ) -> Result { + if !self.supports_sandbox_authentication() { + return Err(Status::unimplemented( + "driver does not support registration", + )); + } + let request = AuthenticateSandboxRequest { + credential: credential.to_string(), + supervisor_registration: true, + }; + let response = self + .driver + .call( + openshell_otel::rpc::AUTHENTICATE_SANDBOX, + None, + |driver| async move { driver.authenticate_sandbox(Request::new(request)).await }, + ) + .await? + .into_inner(); + if response.registration.is_none() { + return Err(Status::unimplemented( + "driver does not support registration", + )); + } + Ok(response) + } + #[must_use] pub(crate) fn telemetry_compute_driver(&self) -> TelemetryComputeDriver { self.telemetry_compute_driver @@ -889,6 +945,27 @@ impl ComputeRuntime { sandbox_token: Option, launch_authentication: Option>, await_main_process_attachment: bool, + ) -> Result { + self.create_sandbox_authenticated_with_guard( + sandbox, + sandbox_token, + launch_authentication, + await_main_process_attachment, + &mut None, + ) + .await + } + + /// Keep provider validation protected through persistence, then release the + /// global guard before driver I/O and completion acquire their own locks. + /// A failed reservation leaves the guard with the caller for its retry. + pub(crate) async fn create_sandbox_authenticated_with_guard( + &self, + sandbox: Sandbox, + sandbox_token: Option, + launch_authentication: Option>, + await_main_process_attachment: bool, + sandbox_sync_guard: &mut Option>, ) -> Result { let sandbox_id = sandbox.object_id().to_string(); let mut sandbox = sandbox; @@ -909,29 +986,22 @@ impl ComputeRuntime { } // Create with MustCreate condition to prevent duplicate creation race - self.sandbox_index.update_from_sandbox(&sandbox); - let labels_map = sandbox.object_labels(); - let labels_json = if labels_map.as_ref().is_none_or(HashMap::is_empty) { - None - } else { - Some( - serde_json::to_string(&labels_map) - .map_err(|e| Status::internal(format!("failed to serialize labels: {e}")))?, - ) - }; + let allocation_claim = warm_pool::allocation_claim(self, &sandbox)?; let result = self .store - .put_if( - Sandbox::object_type(), - &sandbox_id, - sandbox.object_name(), - sandbox.object_workspace(), - &sandbox.encode_to_vec(), - labels_json.as_deref(), + .put_sandbox_allocation( + &sandbox, + allocation_claim.as_ref(), WriteCondition::MustCreate, ) .await .map_err(|e| { + if matches!( + e, + crate::persistence::PersistenceError::AllocationTargetReserved + ) { + return Status::aborted("allocation target is reserved"); + } if matches!( e, crate::persistence::PersistenceError::UniqueViolation { .. } @@ -945,6 +1015,8 @@ impl ComputeRuntime { } })?; + self.sandbox_index.update_from_sandbox(&sandbox); + drop(sandbox_sync_guard.take()); if let Some(token) = sandbox_token && let Some(spec) = driver_sandbox.spec.as_mut() { @@ -954,6 +1026,8 @@ impl ComputeRuntime { spec.await_main_process_attachment = await_main_process_attachment; spec.launch_authentication = launch_authentication.unwrap_or_default(); } + let warm_pair = warm_pool::candidate(&sandbox)?; + let warm_pending = warm_pool::pending(&sandbox); match self .driver .call( @@ -963,6 +1037,7 @@ impl ComputeRuntime { driver .create_sandbox(Request::new(CreateSandboxRequest { sandbox: Some(driver_sandbox), + warm_pair, })) .await }, @@ -970,6 +1045,9 @@ impl ComputeRuntime { .await { Ok(_) => { + if warm_pending { + let _ = warm_pool::completed(self, &sandbox).await; + } // The driver now owns the staged archive and removes the // request directory once it has built the disk. Every other // arm lets the guard drop and clean up. @@ -982,6 +1060,15 @@ impl ComputeRuntime { } Ok(sandbox) } + Err(error) if warm_pending => { + warn!(%error, %sandbox_id, "warm assignment remains pending reconciliation"); + sandbox + .metadata + .as_mut() + .expect("metadata") + .resource_version = result.resource_version; + Ok(sandbox) + } Err(status) if status.code() == Code::AlreadyExists => { let _ = self .store @@ -1194,13 +1281,7 @@ impl ComputeRuntime { .await .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? .ok_or_else(|| Status::not_found("sandbox not found"))?; - if provisioning_deadline::timed_out(&candidate) - && candidate - .status - .as_ref() - .and_then(|status| status.provisioning.as_ref()) - .is_some_and(|record| record.cleanup_completed_time.is_none()) - { + if provisioning_deadline::cleanup_pending(&candidate) { return Err(Status::failed_precondition( "provisioning timeout cleanup is still pending; retry start after compute is reclaimed", )); @@ -1250,7 +1331,6 @@ impl ComputeRuntime { "sandbox must be Stopped, Completed, or a failed main-process Error to start (current phase: {phase:?})" ))); } - if phase == SandboxPhase::Completed || is_failed_main_process_result(¤t) { self.cleanup_stopped_sandbox_sessions(¤t) .await @@ -1268,7 +1348,8 @@ impl ComputeRuntime { let previous = current.clone(); let next_identity = if authority.is_some() - && matches!(phase, SandboxPhase::Stopped | SandboxPhase::Completed) + && (matches!(phase, SandboxPhase::Stopped | SandboxPhase::Completed) + || is_failed_main_process_result(¤t)) { Some(next_runtime_identity(¤t)?) } else { @@ -1445,6 +1526,7 @@ impl ComputeRuntime { driver .create_sandbox(Request::new(CreateSandboxRequest { sandbox: Some(driver_sandbox), + warm_pair: None, })) .await .map(|_| { @@ -1766,11 +1848,14 @@ impl ComputeRuntime { |driver| { let sandbox_id = transition.deleting.object_id().to_string(); let sandbox_name = transition.deleting.object_name().to_string(); + let warm_pair = warm_pool::candidate(&transition.deleting); async move { driver .delete_sandbox(Request::new(DeleteSandboxRequest { sandbox_id, name: sandbox_name, + warm_pair: warm_pair?, + ..Default::default() })) .await } @@ -1808,7 +1893,9 @@ impl ComputeRuntime { }) } Err(err) => { - self.recover_failed_delete(&delete_guard, &transition).await; + if warm_pool::candidate(&transition.deleting)?.is_none() { + self.recover_failed_delete(&delete_guard, &transition).await; + } Err(Status::internal(format!( "delete sandbox failed: {}", err.message() @@ -3729,6 +3816,7 @@ impl ComputeRuntime { .delete_sandbox(Request::new(DeleteSandboxRequest { sandbox_id, name: sandbox_name, + ..Default::default() })) .await } @@ -3949,6 +4037,14 @@ impl ComputeRuntime { } let sandbox = decode_sandbox_record(¤t_record)?; + if warm_pool::pending(&sandbox) + || (warm_pool::candidate(&sandbox) + .map_err(|e| e.to_string())? + .is_some() + && sandbox.phase() == SandboxPhase::Deleting as i32) + { + return Ok(()); + } let age_ms = openshell_core::time::now_ms().saturating_sub(current_record.created_at_ms); if age_ms < grace_ms { @@ -4173,7 +4269,7 @@ fn apply_main_process_exit(sandbox: &mut Sandbox, instance_id: &str, exit_code: sandbox.set_phase(phase as i32); } -fn is_failed_main_process_result(sandbox: &Sandbox) -> bool { +pub fn is_failed_main_process_result(sandbox: &Sandbox) -> bool { sandbox.phase() == SandboxPhase::Error as i32 && sandbox.status.as_ref().is_some_and(|status| { status.exit_code.is_some() @@ -5160,6 +5256,11 @@ fn is_recoverable_error_reason(sandbox: &Sandbox) -> bool { fn apply_lifecycle_phase(sandbox: &mut Sandbox, phase: SandboxPhase, reason: &str, message: &str) { sandbox.set_phase(phase as i32); + if phase == SandboxPhase::Starting { + // Restart can beat allocation recovery after a lost claim + // acknowledgement. Never carry that intent into a new attempt. + warm_pool::clear_pending(sandbox); + } if matches!(phase, SandboxPhase::Stopping | SandboxPhase::Starting) { let status = sandbox.status.get_or_insert_with(Default::default); // Retain the previous instance id as a tombstone until the restarted @@ -5302,6 +5403,25 @@ impl Default for NoopTestDriver { #[cfg(any(test, feature = "test-support"))] #[tonic::async_trait] impl ComputeDriver for NoopTestDriver { + async fn select_warm_pair( + &self, + request: Request, + ) -> Result, Status> + { + let _ = &request; + Err(Status::unimplemented( + "warm pair allocation is not supported", + )) + } + + async fn sync_warm_pools( + &self, + _request: Request, + ) -> Result, Status> + { + Err(Status::unimplemented("warm pools are not supported")) + } + async fn authenticate_sandbox( &self, _request: Request, @@ -5313,6 +5433,7 @@ impl ComputeDriver for NoopTestDriver { Some(Ok(sandbox_id)) => Ok(tonic::Response::new( openshell_core::proto::compute::v1::AuthenticateSandboxResponse { sandbox_id: sandbox_id.clone(), + registration: None, }, )), Some(Err((code, message))) => Err(Status::new(*code, message.clone())), @@ -5513,6 +5634,7 @@ pub fn new_test_runtime_with_driver( supervisor_sessions: Arc::new(SupervisorSessionRegistry::new()), sync_lock: Arc::new(Mutex::new(())), lifecycle_gates: Arc::new(LifecycleGateRegistry::default()), + assignment_notifications: watch::channel(()).0, replica_id: "test-replica".to_string(), rootfs_tar_staging: Arc::new(rootfs_tar::RootfsTarStagingRegistry::disabled()), } @@ -5879,6 +6001,8 @@ mod tests { #[derive(Debug, Default)] struct TestDriver { + warm_create_store: Option>, + warm_create_error: Option, listed_sandboxes: Vec, current_sandboxes: Vec, workspace_rpcs_unimplemented: bool, @@ -5887,6 +6011,29 @@ mod tests { #[tonic::async_trait] impl ComputeDriver for TestDriver { + async fn select_warm_pair( + &self, + request: Request, + ) -> Result< + tonic::Response, + Status, + > { + let _ = &request; + Err(Status::unimplemented( + "warm pair allocation is not supported", + )) + } + + async fn sync_warm_pools( + &self, + _request: Request, + ) -> Result< + tonic::Response, + Status, + > { + Err(Status::unimplemented("warm pools are not supported")) + } + async fn authenticate_sandbox( &self, _request: Request, @@ -5979,8 +6126,22 @@ mod tests { async fn create_sandbox( &self, - _request: Request, + request: Request, ) -> Result, Status> { + if let Some(store) = &self.warm_create_store { + let request = request.into_inner(); + let id = &request.sandbox.as_ref().unwrap().id; + let stored = store + .get_message::(id) + .await + .unwrap() + .expect("candidate must be durable before claim"); + assert_eq!(warm_pool::candidate(&stored).unwrap(), request.warm_pair); + assert!(warm_pool::pending(&stored)); + } + if let Some(code) = self.warm_create_error { + return Err(Status::new(code, "injected claim failure")); + } Ok(tonic::Response::new(CreateSandboxResponse {})) } @@ -6051,6 +6212,7 @@ mod tests { enum ControlledDeleteOutcome { Ok(bool), Error(&'static str), + AlreadyClaimed, } #[derive(Clone)] @@ -6068,6 +6230,16 @@ mod tests { } struct ControlledDriver { + candidates: TestMutex>, + rejected_candidates: TestMutex>, + selections: TestMutex>>, + create_requests: TestMutex>, + create_calls: AtomicUsize, + create_started: Notify, + create_release: Semaphore, + create_blocked: AtomicBool, + create_error: TestMutex>, + warm_delete_requests: TestMutex>, watch_tx: mpsc::UnboundedSender>, watch_rx: TestMutex>>>, watch_started: Notify, @@ -6102,6 +6274,16 @@ mod tests { fn new() -> Arc { let (watch_tx, watch_rx) = mpsc::unbounded_channel(); Arc::new(Self { + candidates: TestMutex::new(Vec::new()), + rejected_candidates: TestMutex::new(Vec::new()), + selections: TestMutex::new(Vec::new()), + create_requests: TestMutex::new(Vec::new()), + create_calls: AtomicUsize::new(0), + create_started: Notify::new(), + create_release: Semaphore::new(0), + create_blocked: AtomicBool::new(false), + create_error: TestMutex::new(None), + warm_delete_requests: TestMutex::new(Vec::new()), watch_tx, watch_rx: TestMutex::new(Some(watch_rx)), watch_started: Notify::new(), @@ -6239,6 +6421,40 @@ mod tests { #[tonic::async_trait] impl ComputeDriver for ControlledDriver { + async fn select_warm_pair( + &self, + request: Request, + ) -> Result< + tonic::Response, + Status, + > { + let request = request.into_inner(); + self.selections + .lock() + .unwrap() + .push(request.excluded_candidate_uids.clone()); + let candidate = self + .candidates + .lock() + .unwrap() + .iter() + .find(|pair| !request.excluded_candidate_uids.contains(&pair.uid)) + .cloned(); + Ok(tonic::Response::new( + openshell_core::proto::compute::v1::SelectWarmPairResponse { candidate }, + )) + } + + async fn sync_warm_pools( + &self, + _request: Request, + ) -> Result< + tonic::Response, + Status, + > { + Err(Status::unimplemented("warm pools are not supported")) + } + async fn authenticate_sandbox( &self, _request: Request, @@ -6327,8 +6543,27 @@ mod tests { async fn create_sandbox( &self, - _request: Request, + request: Request, ) -> Result, Status> { + let request = request.into_inner(); + let warm = request.warm_pair.is_some(); + let rejected = request + .warm_pair + .as_ref() + .is_some_and(|pair| self.rejected_candidates.lock().unwrap().contains(&pair.uid)); + self.create_requests.lock().unwrap().push(request); + self.create_calls.fetch_add(1, Ordering::SeqCst); + self.create_started.notify_one(); + if self.create_blocked.load(Ordering::SeqCst) { + self.create_release.acquire().await.unwrap().forget(); + } + let create_error = *self.create_error.lock().unwrap(); + if rejected { + return Err(Status::aborted("candidate lost its claim")); + } + if warm && let Some(code) = create_error { + return Err(Status::new(code, "injected create failure")); + } Ok(tonic::Response::new(CreateSandboxResponse {})) } @@ -6403,6 +6638,12 @@ mod tests { request: Request, ) -> Result, Status> { let request = request.into_inner(); + if request.warm_pair.is_some() { + self.warm_delete_requests + .lock() + .unwrap() + .push(request.clone()); + } self.delete_requests .lock() .expect("delete requests lock poisoned") @@ -6426,6 +6667,9 @@ mod tests { Ok(tonic::Response::new(DeleteSandboxResponse { deleted })) } ControlledDeleteOutcome::Error(message) => Err(Status::internal(message)), + ControlledDeleteOutcome::AlreadyClaimed => Err(Status::failed_precondition( + "candidate already claimed; recover the assignment", + )), } } @@ -6493,6 +6737,7 @@ mod tests { supervisor_sessions: Arc::new(SupervisorSessionRegistry::new()), sync_lock: Arc::new(Mutex::new(())), lifecycle_gates: Arc::new(LifecycleGateRegistry::default()), + assignment_notifications: watch::channel(()).0, replica_id: "test-replica".to_string(), rootfs_tar_staging: Arc::new(rootfs_tar::RootfsTarStagingRegistry::disabled()), } @@ -7675,6 +7920,29 @@ mod tests { #[tonic::async_trait] impl ComputeDriver for FailingDriver { + async fn select_warm_pair( + &self, + request: Request, + ) -> Result< + tonic::Response, + Status, + > { + let _ = &request; + Err(Status::unimplemented( + "warm pair allocation is not supported", + )) + } + + async fn sync_warm_pools( + &self, + _request: Request, + ) -> Result< + tonic::Response, + Status, + > { + Err(Status::unimplemented("warm pools are not supported")) + } + async fn authenticate_sandbox( &self, _request: Request, @@ -8329,6 +8597,50 @@ mod tests { ); } + #[tokio::test] + async fn authenticated_failed_main_process_restart_rotates_identity() { + let driver = ControlledDriver::new(); + let runtime = test_runtime(driver.clone()).await; + let mut sandbox = + sandbox_record("sb-auth-failed", "sandbox-auth-failed", SandboxPhase::Ready); + apply_main_process_exit(&mut sandbox, "instance-old", 7); + let original = crate::auth::sandbox_session::PersistedSandboxIdentity::read( + &sandbox.metadata.as_ref().unwrap().annotations, + ) + .unwrap(); + runtime.store.put_message(&sandbox).await.unwrap(); + let authority = test_session_authority(); + + runtime + .start_sandbox_authenticated( + sandbox.object_workspace(), + sandbox.object_name(), + Some(&authority), + ) + .await + .unwrap(); + + let stored = runtime + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + let rotated = crate::auth::sandbox_session::PersistedSandboxIdentity::read( + &stored.metadata.as_ref().unwrap().annotations, + ) + .unwrap(); + assert_eq!(stored.phase(), SandboxPhase::Starting as i32); + assert_eq!(rotated.runtime_generation, original.runtime_generation); + assert_eq!(rotated.auth_epoch.get(), original.auth_epoch.get() + 1); + assert_ne!(rotated.gateway_token_id, original.gateway_token_id); + assert!(rotated.refresh_replay.is_none()); + let authentications = driver.start_authentications(); + let authentication: openshell_core::jwt::SandboxLaunchAuthentication = + serde_json::from_slice(&authentications[0]).unwrap(); + assert_eq!(authentication.supervisor.auth_epoch, rotated.auth_epoch); + } + #[tokio::test] async fn request_cancellation_does_not_cancel_start_worker() { let driver = ControlledDriver::new(); @@ -10958,6 +11270,8 @@ mod tests { #[tokio::test] async fn reconcile_store_with_backend_applies_driver_snapshot() { let runtime = test_runtime(Arc::new(TestDriver { + warm_create_store: None, + warm_create_error: None, workspace_rpcs_unimplemented: false, listed_sandboxes: vec![DriverSandbox { id: "sb-1".to_string(), @@ -11149,6 +11463,8 @@ mod tests { #[tokio::test] async fn reconcile_store_with_backend_does_not_recreate_missing_record_from_snapshot() { let runtime = test_runtime(Arc::new(TestDriver { + warm_create_store: None, + warm_create_error: None, workspace_rpcs_unimplemented: false, listed_sandboxes: vec![DriverSandbox { id: "sb-1".to_string(), @@ -12077,6 +12393,7 @@ mod tests { remote .create_sandbox(Request::new(CreateSandboxRequest { sandbox: Some(sandbox.clone()), + ..Default::default() })) .await .unwrap(); @@ -12106,6 +12423,7 @@ mod tests { .delete_sandbox(Request::new(DeleteSandboxRequest { sandbox_id: sandbox.id, name: String::new(), + ..Default::default() })) .await .unwrap(); @@ -12302,6 +12620,80 @@ mod tests { } } + #[tokio::test] + async fn warm_candidate_is_durable_before_claim_and_survives_uncertain_results() { + use openshell_core::proto::compute::v1::WarmPairCandidate; + for error in [None, Some(Code::Unavailable), Some(Code::Aborted)] { + let mut runtime = test_runtime(Arc::new(TestDriver::default())).await; + runtime.driver = TracedDriver::new( + Arc::new(TestDriver { + warm_create_store: Some(runtime.store.clone()), + warm_create_error: error, + ..Default::default() + }), + "test-driver".into(), + ); + let mut sandbox = sandbox_record("warm-logical", "warm", SandboxPhase::Provisioning); + sandbox.status.as_mut().unwrap().provisioning = Some( + provisioning_deadline::new_record(openshell_core::time::now_ms()), + ); + let pair = WarmPairCandidate { + uid: "parent-uid".into(), + name: "physical".into(), + namespace: "openshell".into(), + runtime_generation: "prepared-generation".into(), + ..Default::default() + }; + let annotations = &mut sandbox.metadata.as_mut().unwrap().annotations; + annotations.insert( + "internal.openshell.ai/warm-pair-candidate".into(), + hex::encode(pair.encode_to_vec()), + ); + annotations.insert( + "internal.openshell.ai/warm-pair-pending".into(), + "true".into(), + ); + let mut assignments = runtime.subscribe_assignments(); + runtime.create_sandbox(sandbox, None, false).await.unwrap(); + assert_eq!(assignments.has_changed().unwrap(), error.is_none()); + if error.is_none() { + // A claim between observation and waiting must not lose its wakeup. + tokio::time::timeout(Duration::from_millis(100), assignments.changed()) + .await + .expect("successful claim should wake registration immediately") + .unwrap(); + } + let stored = runtime + .store + .get_message::("warm-logical") + .await + .unwrap() + .unwrap(); + assert_eq!(warm_pool::candidate(&stored).unwrap(), Some(pair)); + assert_eq!(warm_pool::pending(&stored), error.is_some()); + if error.is_some() { + let record = runtime + .store + .get(Sandbox::object_type(), "warm-logical") + .await + .unwrap() + .unwrap(); + runtime + .prune_missing_sandbox(record, i64::MAX, 0) + .await + .unwrap(); + assert!( + runtime + .store + .get_message::("warm-logical") + .await + .unwrap() + .is_some() + ); + } + } + } + #[tokio::test] async fn create_sandbox_returns_resource_version_one() { let runtime = test_runtime(Arc::new(TestDriver::default())).await; @@ -12520,6 +12912,849 @@ mod tests { .unwrap() } + fn warm_recovery_state(mut runtime: ComputeRuntime) -> crate::ServerState { + // Fresh process-local fences with the same durable store and driver. + runtime.lifecycle_gates = Arc::new(LifecycleGateRegistry::default()); + runtime.sync_lock = Arc::new(Mutex::new(())); + let mut state = crate::ServerState::new( + crate::Config::new(None).with_credential_drivers(["test-static"]), + runtime.store.clone(), + runtime.clone(), + runtime.sandbox_index.clone(), + runtime.sandbox_watch_bus.clone(), + runtime.tracing_log_bus.clone(), + runtime.supervisor_sessions, + None, + ); + let key = openshell_bootstrap::jwt::generate_jwt_key().unwrap(); + state.sandbox_session_jwt_authority = Some(Arc::new( + crate::auth::sandbox_jwt::SandboxSessionJwtAuthority::from_pem( + key.signing_key_pem.as_bytes(), + key.public_key_pem.as_bytes(), + key.kid, + "gateway", + Duration::from_hours(1), + ) + .unwrap(), + )); + state + } + + async fn seed_warm_attempt(runtime: &ComputeRuntime, warm: bool, now_ms: i64) -> Sandbox { + use crate::auth::sandbox_session::PersistedSandboxIdentity; + use openshell_core::proto::compute::v1::WarmPairCandidate; + let mut sandbox = seed_provisioning_attempt(runtime).await; + sandbox.status.as_mut().unwrap().provisioning = + Some(provisioning_deadline::new_record(now_ms)); + let mut identity = PersistedSandboxIdentity::new().unwrap(); + warm_pool::bind_candidate( + &mut sandbox, + &mut identity, + warm.then(|| WarmPairCandidate { + namespace: "openshell".into(), + name: "physical".into(), + uid: "pair-uid".into(), + runtime_generation: "prepared-generation".into(), + ..Default::default() + }), + ) + .unwrap(); + runtime.store.put_message(&sandbox).await.unwrap(); + runtime + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap() + } + + fn allocation_pair(index: usize) -> openshell_core::proto::compute::v1::WarmPairCandidate { + openshell_core::proto::compute::v1::WarmPairCandidate { + namespace: "openshell".into(), + name: format!("physical-{index}"), + uid: format!("uid-{index}"), + runtime_generation: format!("generation-{index}"), + template_id: "pool-template".into(), + ..Default::default() + } + } + + async fn unpersisted_warm_intent(runtime: &ComputeRuntime, id: &str) -> Sandbox { + use openshell_core::proto::{ObjectMeta, SandboxWorkloadTemplateProvenance}; + if runtime + .store + .get_message::("pool-template") + .await + .unwrap() + .is_none() + { + runtime + .store + .put_message(&SandboxWorkloadTemplate { + metadata: Some(ObjectMeta { + id: "pool-template".into(), + name: "pool-template".into(), + workspace: "default".into(), + ..Default::default() + }), + ..Default::default() + }) + .await + .unwrap(); + } + let template = runtime + .store + .get_message::("pool-template") + .await + .unwrap() + .unwrap(); + let mut sandbox = sandbox_record(id, id, SandboxPhase::Provisioning); + sandbox.spec = Some(SandboxSpec::default()); + sandbox.created_from_workload_template = Some(SandboxWorkloadTemplateProvenance { + name: "pool-template".into(), + resource_version: template.metadata.unwrap().resource_version.to_string(), + }); + sandbox.status.as_mut().unwrap().provisioning = Some(provisioning_deadline::new_record( + openshell_core::time::now_ms(), + )); + warm_pool::bind_candidate( + &mut sandbox, + &mut crate::auth::sandbox_session::PersistedSandboxIdentity::new().unwrap(), + Some(allocation_pair(0)), + ) + .unwrap(); + sandbox + } + + #[tokio::test] + async fn warm_create_with_providers_releases_global_guard_after_persistence() { + use crate::grpc::test_support::{authed_request, seed_example_provider_profiles}; + use openshell_core::proto::open_shell_server::OpenShell; + use openshell_core::proto::{ + ObjectMeta, Provider, SandboxWorkloadConfig, SandboxWorkloadTemplateSpec, + }; + + for (collision, uncertain) in [(false, false), (true, false), (false, true)] { + let driver = ControlledDriver::new(); + *driver.candidates.lock().unwrap() = vec![allocation_pair(0), allocation_pair(1)]; + driver.create_blocked.store(true, Ordering::SeqCst); + if uncertain { + *driver.create_error.lock().unwrap() = Some(Code::Unavailable); + } + let runtime = test_runtime(driver.clone()).await; + crate::ensure_default_workspace(&runtime.store) + .await + .unwrap(); + seed_example_provider_profiles(&runtime.store).await; + runtime + .store + .put_message(&Provider { + metadata: Some(ObjectMeta { + id: "provider-github".into(), + name: "work-github".into(), + workspace: "default".into(), + ..Default::default() + }), + r#type: "github".into(), + credentials: HashMap::from([("TOKEN".into(), "test-secret".into())]), + profile_workspace: "default".into(), + ..Default::default() + }) + .await + .unwrap(); + let winner = unpersisted_warm_intent(&runtime, "winner").await; + if collision { + runtime + .store + .put_sandbox_allocation( + &winner, + warm_pool::allocation_claim(&runtime, &winner) + .unwrap() + .as_ref(), + WriteCondition::MustCreate, + ) + .await + .unwrap(); + } + let mut template = runtime + .store + .get_message::("pool-template") + .await + .unwrap() + .unwrap(); + template.spec = Some(SandboxWorkloadTemplateSpec { + workload: Some(SandboxWorkloadConfig { + image: "test-image".into(), + ..Default::default() + }), + ..Default::default() + }); + runtime.store.put_message(&template).await.unwrap(); + let state = Arc::new(warm_recovery_state(runtime)); + let guard = state.compute.sandbox_sync_guard().await; + let task_state = state.clone(); + let mut task = tokio::spawn(async move { + Box::pin( + crate::grpc::OpenShellService::new(task_state).create_sandbox(authed_request( + openshell_core::proto::CreateSandboxRequest { + name: "provider-warm".into(), + spec: Some(SandboxSpec { + providers: vec!["work-github".into()], + ..Default::default() + }), + workload_template: "pool-template".into(), + workspace_scope: Some(openshell_core::proto::workspace_selector( + "default", + )), + ..Default::default() + }, + )), + ) + .await + }); + let waiting = tokio::time::timeout(Duration::from_millis(20), &mut task).await; + assert!( + waiting.is_err(), + "provider validation must wait for the global guard: {waiting:?}" + ); + drop(guard); + tokio::time::timeout(Duration::from_secs(5), driver.create_started.notified()) + .await + .expect("create should reach the driver"); + + // The driver is blocked: other operations can already take the + // global lock and observe the committed provider attachment. + let guard = + tokio::time::timeout(Duration::from_secs(1), state.compute.sandbox_sync_guard()) + .await + .expect("driver I/O must not hold the global guard"); + let stored = state + .store + .get_message_by_name::("default", "provider-warm") + .await + .unwrap() + .expect("intent must be persisted before releasing the guard"); + assert_eq!(stored.spec.as_ref().unwrap().providers, vec!["work-github"]); + assert_eq!( + warm_pool::candidate(&stored).unwrap(), + Some(allocation_pair(usize::from(collision))) + ); + let mut competing_claim = warm_pool::allocation_claim(&state.compute, &stored) + .unwrap() + .unwrap(); + competing_claim.sandbox_id = "contender".into(); + assert!( + state + .store + .allocation_conflicts(&competing_claim) + .await + .unwrap() + ); + drop(guard); + + driver.create_blocked.store(false, Ordering::SeqCst); + driver.create_release.add_permits(1); + let result = tokio::time::timeout(Duration::from_secs(5), &mut task).await; + if result.is_err() { + task.abort(); + } + let sandbox = result + .expect("completion and recovery must not deadlock") + .unwrap() + .unwrap() + .into_inner() + .sandbox + .unwrap(); + assert_eq!(warm_pool::pending(&sandbox), uncertain); + assert_eq!(sandbox.spec.unwrap().providers, vec!["work-github"]); + assert_eq!( + driver.create_calls.load(Ordering::SeqCst), + if uncertain { 2 } else { 1 } + ); + } + } + + #[tokio::test] + async fn warm_concurrent_creates_reserve_distinct_targets_without_waiting_for_recovery() { + let driver = ControlledDriver::new(); + *driver.candidates.lock().unwrap() = vec![allocation_pair(0), allocation_pair(1)]; + driver.create_blocked.store(true, Ordering::SeqCst); + let runtime = test_runtime(driver.clone()).await; + let first = unpersisted_warm_intent(&runtime, "first").await; + let second = unpersisted_warm_intent(&runtime, "second").await; + let left = warm_recovery_state(runtime.clone()); + let right = warm_recovery_state(runtime.clone()); + let a = tokio::spawn(async move { Box::pin(warm_pool::create(&left, first, None)).await }); + tokio::time::timeout(Duration::from_secs(5), driver.create_started.notified()) + .await + .unwrap(); + let b = + tokio::spawn(async move { Box::pin(warm_pool::create(&right, second, None)).await }); + tokio::time::timeout(Duration::from_secs(5), driver.create_started.notified()) + .await + .unwrap(); + let requests = driver.create_requests.lock().unwrap().clone(); + assert_eq!(requests.len(), 2); + assert_eq!(requests[0].warm_pair, Some(allocation_pair(0))); + assert_eq!(requests[1].warm_pair, Some(allocation_pair(1))); + assert_eq!( + driver.delete_calls(), + 0, + "the losing reservation must never retire the winner's target" + ); + assert_eq!( + *driver.selections.lock().unwrap(), + vec![vec!["uid-0".to_string()]] + ); + driver.create_blocked.store(false, Ordering::SeqCst); + driver.create_release.add_permits(2); + let (first, second) = tokio::time::timeout(Duration::from_secs(5), async { + (a.await.unwrap().unwrap(), b.await.unwrap().unwrap()) + }) + .await + .unwrap(); + assert!(!warm_pool::pending(&first)); + assert!(!warm_pool::pending(&second)); + assert_ne!( + warm_pool::candidate(&first).unwrap(), + warm_pool::candidate(&second).unwrap() + ); + assert_eq!(driver.create_calls.load(Ordering::SeqCst), 2); + } + + #[tokio::test] + async fn warm_recovery_claims_replacement_immediately_and_bounds_cold_fallback() { + for exhaust in [false, true] { + let driver = ControlledDriver::new(); + *driver.candidates.lock().unwrap() = (0..8).map(allocation_pair).collect(); + *driver.rejected_candidates.lock().unwrap() = if exhaust { + (0..8).map(|index| allocation_pair(index).uid).collect() + } else { + vec!["uid-0".into()] + }; + let runtime = test_runtime(driver.clone()).await; + let sandbox = unpersisted_warm_intent(&runtime, "recover").await; + let attempt = sandbox + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap() + .clone(); + runtime.store.put_message(&sandbox).await.unwrap(); + let state = warm_recovery_state(runtime.clone()); + tokio::time::timeout( + Duration::from_secs(5), + warm_pool::recover_one(&state, "recover"), + ) + .await + .unwrap() + .unwrap(); + let current = runtime + .store + .get_message::("recover") + .await + .unwrap() + .unwrap(); + assert!(!warm_pool::pending(¤t)); + assert_eq!( + current + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap() + .attempt_id, + attempt.attempt_id + ); + assert_eq!( + current + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap() + .deadline, + attempt.deadline + ); + let requests = driver.create_requests.lock().unwrap().clone(); + if exhaust { + assert_eq!( + requests.len(), + 5, + "four warm attempts then immediate cold fallback" + ); + assert!(requests.last().unwrap().warm_pair.is_none()); + assert!(warm_pool::candidate(¤t).unwrap().is_none()); + } else { + assert_eq!(requests.len(), 2); + assert_eq!(requests[1].warm_pair, Some(allocation_pair(1))); + assert_eq!( + sandbox_runtime_generation(¤t).unwrap().as_str(), + "generation-1" + ); + } + assert_eq!(driver.delete_calls(), if exhaust { 4 } else { 1 }); + let mut previous = warm_pool::allocation_claim(&runtime, &sandbox) + .unwrap() + .unwrap(); + previous.sandbox_id = "probe".into(); + assert!( + !runtime.store.allocation_conflicts(&previous).await.unwrap(), + "retired target reservation must be released" + ); + } + } + + #[tokio::test] + async fn warm_uncertain_claim_keeps_reservation_across_gateway_restart() { + let driver = ControlledDriver::new(); + *driver.create_error.lock().unwrap() = Some(Code::Unavailable); + let runtime = test_runtime(driver.clone()).await; + let sandbox = unpersisted_warm_intent(&runtime, "uncertain").await; + runtime.store.put_message(&sandbox).await.unwrap(); + assert_eq!( + warm_pool::recover_one(&warm_recovery_state(runtime.clone()), "uncertain") + .await + .unwrap_err() + .code(), + Code::Unavailable + ); + let mut contender = warm_pool::allocation_claim(&runtime, &sandbox) + .unwrap() + .unwrap(); + contender.sandbox_id = "other".into(); + assert!( + runtime + .store + .allocation_conflicts(&contender) + .await + .unwrap() + ); + assert_eq!(driver.delete_calls(), 0); + assert!(driver.selections.lock().unwrap().is_empty()); + *driver.create_error.lock().unwrap() = None; + warm_pool::recover_one(&warm_recovery_state(runtime.clone()), "uncertain") + .await + .unwrap(); + let current = runtime + .store + .get_message::("uncertain") + .await + .unwrap() + .unwrap(); + assert!(!warm_pool::pending(¤t)); + assert_eq!( + warm_pool::candidate(¤t).unwrap(), + warm_pool::candidate(&sandbox).unwrap() + ); + assert_eq!(driver.create_requests.lock().unwrap().len(), 2); + } + + #[tokio::test] + async fn warm_restart_after_lost_claim_acknowledgement_preserves_assignment() { + use crate::auth::sandbox_session::PersistedSandboxIdentity; + + for exit_code in [0, 1] { + for recover_before_restart in [false, true] { + let driver = ControlledDriver::new(); + *driver.create_error.lock().unwrap() = Some(Code::Unavailable); + let runtime = test_runtime(driver.clone()).await; + let sandbox = unpersisted_warm_intent(&runtime, "uncertain").await; + runtime.store.put_message(&sandbox).await.unwrap(); + let state = warm_recovery_state(runtime.clone()); + + // Reserve the pair, but lose the acknowledgement of its claim. + assert_eq!( + warm_pool::recover_one(&state, "uncertain") + .await + .unwrap_err() + .code(), + Code::Unavailable + ); + *driver.create_error.lock().unwrap() = None; + let mut finished = runtime + .store + .get_message::("uncertain") + .await + .unwrap() + .unwrap(); + let claim = warm_pool::allocation_claim(&runtime, &finished) + .unwrap() + .unwrap(); + assert!(warm_pool::pending(&finished)); + + // The workload can finish before recovery ever observes Ready. + apply_main_process_exit(&mut finished, "first-process", exit_code); + runtime.store.put_message(&finished).await.unwrap(); + if recover_before_restart { + warm_pool::recover_one(&state, "uncertain").await.unwrap(); + let recovered = runtime + .store + .get_message::("uncertain") + .await + .unwrap() + .unwrap(); + assert!(!warm_pool::pending(&recovered)); + assert_eq!(recovered.phase(), finished.phase()); + assert_eq!( + recovered.status.as_ref().unwrap().exit_code, + Some(exit_code) + ); + } + + // Also cover restart winning the race with background recovery. + // The driver returns before readiness, leaving recovery a Starting row. + let starting = runtime.start_sandbox("default", "uncertain").await.unwrap(); + assert_eq!(starting.phase(), SandboxPhase::Starting as i32); + assert_ne!( + starting + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap() + .attempt_id, + claim.attempt_id + ); + assert!(!warm_pool::pending(&starting)); + let identity = PersistedSandboxIdentity::read( + &starting.metadata.as_ref().unwrap().annotations, + ) + .unwrap(); + + // A different gateway replica must not allocate a replacement or + // change the credentials supplied to the restarting supervisor. + warm_pool::recover_one(&warm_recovery_state(runtime.clone()), "uncertain") + .await + .unwrap(); + let current = runtime + .store + .get_message::("uncertain") + .await + .unwrap() + .unwrap(); + assert_eq!( + PersistedSandboxIdentity::read(¤t.metadata.as_ref().unwrap().annotations) + .unwrap(), + identity + ); + assert_eq!( + warm_pool::candidate(¤t).unwrap(), + Some(allocation_pair(0)) + ); + assert_eq!(driver.start_calls(), 1); + assert_eq!(driver.create_calls.load(Ordering::SeqCst), 1); + assert!(driver.selections.lock().unwrap().is_empty()); + assert_eq!(driver.delete_calls(), 0); + assert!(!runtime.store.allocation_conflicts(&claim).await.unwrap()); + let mut contender = claim; + contender.sandbox_id = "other".into(); + assert!( + runtime + .store + .allocation_conflicts(&contender) + .await + .unwrap() + ); + } + } + } + + #[tokio::test] + async fn warm_recovery_reclaims_expired_attempts_without_allocating() { + for warm in [false, true] { + for previously_completed in [false, true] { + let driver = ControlledDriver::new(); + let runtime = test_runtime(driver.clone()).await; + let mut sandbox = seed_warm_attempt(&runtime, warm, 0).await; + let attempt = sandbox + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap() + .attempt_id + .clone(); + if previously_completed { + sandbox = runtime + .claim_provisioning_timeout(&sandbox, 300_000) + .await + .unwrap() + .unwrap(); + sandbox + .status + .as_mut() + .unwrap() + .provisioning + .as_mut() + .unwrap() + .cleanup_completed_time = + openshell_core::time::timestamp_from_millis(300_001).ok(); + if warm { + // An older late completion could also clear pending, + // leaving only the candidate as evidence of unfinished cleanup. + sandbox + .metadata + .as_mut() + .unwrap() + .annotations + .remove("internal.openshell.ai/warm-pair-pending"); + } + runtime.store.put_message(&sandbox).await.unwrap(); + assert_eq!( + runtime + .start_sandbox("default", "sandbox-ttl") + .await + .unwrap_err() + .code(), + Code::FailedPrecondition + ); + } + let state = warm_recovery_state(runtime.clone()); + warm_pool::recover_one(&state, "sb-ttl").await.unwrap(); + let cleaned = runtime + .store + .get_message::("sb-ttl") + .await + .unwrap() + .unwrap(); + assert_eq!(cleaned.phase(), SandboxPhase::Error as i32); + assert!(provisioning_deadline::timed_out(&cleaned)); + assert!(!provisioning_deadline::cleanup_pending(&cleaned)); + assert!(!warm_pool::has_allocation(&cleaned)); + assert_eq!(driver.create_calls.load(Ordering::SeqCst), 0); + assert_eq!(driver.stop_calls(), 1); + let requests = driver.warm_delete_requests.lock().unwrap().clone(); + assert_eq!(requests.len(), usize::from(warm)); + if warm { + assert!(requests[0].only_unassigned_pair); + assert_eq!( + requests[0].warm_pair, + warm_pool::candidate(&sandbox).unwrap() + ); + } + // Repeated recovery cannot resurrect compute or redo successful cleanup. + warm_pool::recover_one(&state, "sb-ttl").await.unwrap(); + assert_eq!(driver.stop_calls(), 1); + assert_eq!(driver.create_calls.load(Ordering::SeqCst), 0); + let restarted = runtime + .start_sandbox("default", "sandbox-ttl") + .await + .unwrap(); + assert_ne!( + restarted + .status + .as_ref() + .unwrap() + .provisioning + .as_ref() + .unwrap() + .attempt_id, + attempt + ); + assert!(!provisioning_deadline::timed_out(&restarted)); + assert_eq!(driver.start_calls(), 1); + } + } + } + + #[tokio::test] + async fn warm_timeout_retirement_failure_survives_restart_and_claim_race() { + let driver = ControlledDriver::new(); + driver.set_delete_outcome(ControlledDeleteOutcome::Error("retirement unavailable")); + let runtime = test_runtime(driver.clone()).await; + let sandbox = seed_warm_attempt(&runtime, true, 0).await; + warm_pool::recover_one(&warm_recovery_state(runtime.clone()), "sb-ttl") + .await + .unwrap(); + let mut failed = runtime + .store + .get_message::("sb-ttl") + .await + .unwrap() + .unwrap(); + assert!(provisioning_deadline::cleanup_pending(&failed)); + assert!(warm_pool::pending(&failed)); + assert_eq!( + warm_pool::candidate(&failed).unwrap(), + warm_pool::candidate(&sandbox).unwrap() + ); + let record = failed + .status + .as_mut() + .unwrap() + .provisioning + .as_mut() + .unwrap(); + assert!(record.cleanup_completed_time.is_none()); + assert!(!record.cleanup_error.is_empty()); + assert_eq!(driver.stop_calls(), 0); + assert_eq!( + runtime + .start_sandbox("default", "sandbox-ttl") + .await + .unwrap_err() + .code(), + Code::FailedPrecondition + ); + // Simulate the backoff elapsing and a claim winning before retirement retries. + record.cleanup_retry_time = None; + runtime.store.put_message(&failed).await.unwrap(); + driver.set_delete_outcome(ControlledDeleteOutcome::AlreadyClaimed); + warm_pool::recover_one(&warm_recovery_state(runtime.clone()), "sb-ttl") + .await + .unwrap(); + let cleaned = runtime + .store + .get_message::("sb-ttl") + .await + .unwrap() + .unwrap(); + assert!(!provisioning_deadline::cleanup_pending(&cleaned)); + assert!(!warm_pool::has_allocation(&cleaned)); + assert_eq!(driver.stop_calls(), 1); + assert!( + driver + .warm_delete_requests + .lock() + .unwrap() + .iter() + .all(|request| request.only_unassigned_pair) + ); + assert_eq!(driver.create_calls.load(Ordering::SeqCst), 0); + } + + #[tokio::test] + async fn warm_completion_cannot_clear_expired_or_replaced_attempts() { + for change_attempt in [false, true] { + let runtime = test_runtime(ControlledDriver::new()).await; + // None candidates must still distinguish cold fallback generations. + let expected = seed_warm_attempt(&runtime, false, openshell_core::time::now_ms()).await; + let mut replacement = expected.clone(); + if change_attempt { + replacement.status.as_mut().unwrap().provisioning = Some( + provisioning_deadline::new_record(openshell_core::time::now_ms()), + ); + } else { + crate::auth::sandbox_session::PersistedSandboxIdentity::new() + .unwrap() + .write(&mut replacement.metadata.as_mut().unwrap().annotations); + } + runtime.store.put_message(&replacement).await.unwrap(); + warm_pool::completed(&runtime, &expected).await.unwrap(); + let current = runtime + .store + .get_message::("sb-ttl") + .await + .unwrap() + .unwrap(); + assert!(warm_pool::pending(¤t)); + let expired = runtime + .claim_provisioning_timeout(¤t, openshell_core::time::now_ms() + 300_001) + .await + .unwrap() + .unwrap(); + warm_pool::completed(&runtime, ¤t).await.unwrap(); + let after = runtime + .store + .get_message::("sb-ttl") + .await + .unwrap() + .unwrap(); + assert_eq!(after, expired); + } + } + + #[tokio::test] + async fn warm_recovery_fences_driver_results_after_expiry() { + for outcome in [None, Some(Code::Aborted)] { + let driver = ControlledDriver::new(); + driver.create_blocked.store(true, Ordering::SeqCst); + *driver.create_error.lock().unwrap() = outcome; + let runtime = test_runtime(driver.clone()).await; + seed_warm_attempt(&runtime, true, openshell_core::time::now_ms()).await; + let state = warm_recovery_state(runtime.clone()); + let worker = + tokio::spawn(async move { warm_pool::recover_one(&state, "sb-ttl").await }); + tokio::time::timeout(Duration::from_secs(5), driver.create_started.notified()) + .await + .unwrap(); + let current = runtime + .store + .get_message::("sb-ttl") + .await + .unwrap() + .unwrap(); + runtime + .claim_provisioning_timeout(¤t, openshell_core::time::now_ms() + 300_001) + .await + .unwrap() + .unwrap(); + driver.create_release.add_permits(1); + worker.await.unwrap().unwrap(); + let cleaned = runtime + .store + .get_message::("sb-ttl") + .await + .unwrap() + .unwrap(); + assert!(provisioning_deadline::timed_out(&cleaned)); + assert!(!provisioning_deadline::cleanup_pending(&cleaned)); + assert!(!warm_pool::has_allocation(&cleaned)); + assert_eq!(driver.create_calls.load(Ordering::SeqCst), 1); + assert_eq!(driver.delete_calls(), 1); + assert_eq!(driver.stop_calls(), 1); + } + } + + #[tokio::test] + async fn warm_recovery_cannot_rebind_after_expiry_during_candidate_retirement() { + let driver = ControlledDriver::new(); + *driver.create_error.lock().unwrap() = Some(Code::Aborted); + driver.block_delete(); + let runtime = test_runtime(driver.clone()).await; + seed_warm_attempt(&runtime, true, openshell_core::time::now_ms()).await; + let state = warm_recovery_state(runtime.clone()); + let worker = tokio::spawn(async move { warm_pool::recover_one(&state, "sb-ttl").await }); + tokio::time::timeout(Duration::from_secs(5), driver.delete_started.notified()) + .await + .unwrap(); + let current = runtime + .store + .get_message::("sb-ttl") + .await + .unwrap() + .unwrap(); + runtime + .claim_provisioning_timeout(¤t, openshell_core::time::now_ms() + 300_001) + .await + .unwrap() + .unwrap(); + driver.delete_blocked.store(false, Ordering::SeqCst); + driver.release_delete(); + worker.await.unwrap().unwrap(); + let cleaned = runtime + .store + .get_message::("sb-ttl") + .await + .unwrap() + .unwrap(); + assert!(provisioning_deadline::timed_out(&cleaned)); + assert!(!warm_pool::has_allocation(&cleaned)); + assert_eq!( + sandbox_runtime_generation(&cleaned).unwrap(), + sandbox_runtime_generation(¤t).unwrap() + ); + assert_eq!(driver.create_calls.load(Ordering::SeqCst), 1); + assert_eq!(driver.stop_calls(), 1); + } + #[tokio::test] async fn provisioning_timeout_persists_error_before_cleanup_and_preserves_record() { let driver = ControlledDriver::new(); diff --git a/crates/openshell-server/src/compute/provisioning_deadline.rs b/crates/openshell-server/src/compute/provisioning_deadline.rs index 54d1f83682..d686700b13 100644 --- a/crates/openshell-server/src/compute/provisioning_deadline.rs +++ b/crates/openshell-server/src/compute/provisioning_deadline.rs @@ -197,6 +197,18 @@ pub fn timed_out(sandbox: &openshell_core::proto::Sandbox) -> bool { .is_some_and(|record| record.timeout_time.is_some()) } +/// Older gateways could acknowledge cleanup without retiring allocation intent. +/// Such records must be reclaimed again before an explicit start is permitted. +pub(super) fn cleanup_pending(sandbox: &openshell_core::proto::Sandbox) -> bool { + timed_out(sandbox) + && (super::warm_pool::has_allocation(sandbox) + || sandbox + .status + .as_ref() + .and_then(|status| status.provisioning.as_ref()) + .is_some_and(|record| record.cleanup_completed_time.is_none())) +} + /// Create an independent attempt. Supervisor reconnects must never call this. pub fn new_record(now_ms: i64) -> SandboxProvisioning { let mut record = SandboxProvisioning::default(); @@ -316,6 +328,34 @@ pub async fn refresh_configuration( } impl super::ComputeRuntime { + /// The caller holds the global configuration guard. Refresh committed + /// source clocks before deciding expiry, both in the scanner and recovery. + pub(super) async fn refresh_provisioning_deadline( + &self, + mut current: openshell_core::proto::Sandbox, + now_ms: i64, + ) -> Result { + use openshell_core::{ObjectId, proto::Sandbox}; + let previous = current.clone(); + refresh_configuration(&self.store, &mut current, now_ms).await?; + if current != previous { + current = self + .store + .update_message_cas::( + previous.object_id(), + super::sandbox_resource_version(&previous), + |sandbox| sandbox.status.clone_from(¤t.status), + ) + .await + .map_err(|e| e.to_string())?; + self.sandbox_watch_bus.notify(previous.object_id()); + } + Ok(self + .claim_provisioning_timeout(¤t, now_ms) + .await? + .unwrap_or(current)) + } + pub(super) async fn provisioning_loop( self: std::sync::Arc, mut cancel: tokio::sync::watch::Receiver, @@ -362,7 +402,7 @@ impl super::ComputeRuntime { // lifecycle gate. Cleanup waits for that gate; Error never waits // for compute I/O, matching the existing driver-observation fence. let global = self.sync_lock.clone().lock_owned().await; - let Some(mut current) = self + let Some(current) = self .store .get_message::(&record.id) .await @@ -370,36 +410,19 @@ impl super::ComputeRuntime { else { continue; }; - let previous = current.clone(); - refresh_configuration(&self.store, &mut current, now_ms).await?; - if current != previous { - current = self - .store - .update_message_cas::( - &record.id, - super::sandbox_resource_version(&previous), - |sandbox| sandbox.status.clone_from(¤t.status), - ) - .await - .map_err(|e| e.to_string())?; - self.sandbox_watch_bus.notify(&record.id); - } - if let Some(expired) = self.claim_provisioning_timeout(¤t, now_ms).await? { - current = expired; - } + let current = self.refresh_provisioning_deadline(current, now_ms).await?; drop(global); - if timed_out(¤t) + if cleanup_pending(¤t) && current .status .as_ref() .and_then(|status| status.provisioning.as_ref()) .is_some_and(|record| { - record.cleanup_completed_time.is_none() - && record - .cleanup_retry_time - .as_ref() - .and_then(|t| timestamp_to_millis(t).ok()) - .is_none_or(|t| t <= now_ms) + record + .cleanup_retry_time + .as_ref() + .and_then(|t| timestamp_to_millis(t).ok()) + .is_none_or(|t| t <= now_ms) }) { let Ok(guard) = self.lifecycle_gates.gate_for(&record.id).try_lock_owned() else { @@ -537,7 +560,7 @@ impl super::ComputeRuntime { else { return Ok(()); }; - if record.timeout_time.is_none() || record.cleanup_completed_time.is_some() { + if !cleanup_pending(expired) { return Ok(()); } let now_ms = openshell_core::time::now_ms(); @@ -560,12 +583,13 @@ impl super::ComputeRuntime { expired.object_id(), super::sandbox_resource_version(expired), |sandbox| { - sandbox + let record = sandbox .status .as_mut() .and_then(|status| status.provisioning.as_mut()) - .expect("timeout record exists") - .cleanup_retry_time = + .expect("timeout record exists"); + record.cleanup_completed_time = None; + record.cleanup_retry_time = timestamp_from_millis(now_ms.saturating_add(35_000)).ok(); }, ) @@ -574,27 +598,39 @@ impl super::ComputeRuntime { } let sandbox_id = expired.object_id().to_string(); let sandbox_name = expired.object_name().to_string(); - let result = tokio::time::timeout( - std::time::Duration::from_secs(30), - self.driver.call( - openshell_otel::rpc::STOP_SANDBOX, - Some(&sandbox_id), - |driver| { - let sandbox_id = sandbox_id.clone(); - async move { - driver - .stop_sandbox(tonic::Request::new(StopSandboxRequest { - sandbox_id, - name: sandbox_name, - })) - .await - } - }, - ), - ) + let result = tokio::time::timeout(std::time::Duration::from_secs(30), async { + super::warm_pool::retire_pending_allocation(self, expired).await?; + match self + .driver + .call( + openshell_otel::rpc::STOP_SANDBOX, + Some(&sandbox_id), + |driver| { + let sandbox_id = sandbox_id.clone(); + async move { + driver + .stop_sandbox(tonic::Request::new(StopSandboxRequest { + sandbox_id, + name: sandbox_name, + })) + .await + } + }, + ) + .await + { + Ok(_) => Ok(()), + Err(error) if error.code() == tonic::Code::NotFound => Ok(()), + Err(error) => Err(error), + } + }) .await; - let reclaimed = matches!(&result, Ok(Ok(_))) - || matches!(&result, Ok(Err(error)) if error.code() == tonic::Code::NotFound); + let reclaimed = matches!(&result, Ok(Ok(()))); + if reclaimed { + super::warm_pool::release_allocation(self, expired) + .await + .map_err(|e| e.to_string())?; + } let _global_guard = self.lock_global_for_lifecycle(lifecycle_guard).await; let Some(current) = self .store @@ -630,6 +666,7 @@ impl super::ComputeRuntime { record.cleanup_completed_time = timestamp_from_millis(completed_at_ms).ok(); record.cleanup_error.clear(); record.cleanup_retry_time = None; + super::warm_pool::clear_allocation(sandbox); } else { record.cleanup_error = "Compute reclamation is pending; the gateway will retry".into(); diff --git a/crates/openshell-server/src/compute/warm_pool.rs b/crates/openshell-server/src/compute/warm_pool.rs new file mode 100644 index 0000000000..ac20ea01f9 --- /dev/null +++ b/crates/openshell-server/src/compute/warm_pool.rs @@ -0,0 +1,1032 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Publish complete template snapshots; the driver owns inventory and election. +use super::{SandboxWorkloadTemplate, driver_sandbox_spec_from_public}; +use crate::persistence::ObjectType; +use openshell_core::proto::compute::v1::{SyncWarmPoolsRequest, WarmPoolTarget}; +use openshell_core::{ObjectId, ObjectName}; +use std::{sync::Arc, time::Duration}; +use tokio::sync::watch; +use tonic::{Code, Request, Status}; +use tracing::warn; + +pub fn spawn(state: Arc, mut shutdown: watch::Receiver) { + let mut recovery_shutdown = shutdown.clone(); + tokio::spawn(async move { + // Slow claim recovery must not prevent refreshing the preparation snapshot. + // If publication reports an unsupported driver, stop both loops. + let publication = async { + let mut interval = tokio::time::interval(Duration::from_secs(15)); + loop { + tokio::select! { _ = shutdown.changed() => break, _ = interval.tick() => {} } + if *shutdown.borrow() { + break; + } + if let Err(error) = publish(&state).await { + if error.code() == Code::Unimplemented { + break; + } + warn!(%error, "could not refresh warm pool targets"); + } + } + }; + let recovery = async { + let mut interval = tokio::time::interval(Duration::from_secs(15)); + loop { + tokio::select! { _ = recovery_shutdown.changed() => break, _ = interval.tick() => {} } + if *recovery_shutdown.borrow() { + break; + } + tokio::select! { + _ = recovery_shutdown.changed() => break, + result = recover(&state) => { + if let Err(error) = result { warn!(%error, "warm allocation recovery failed"); } + } + } + } + }; + tokio::select! { () = publication => {}, () = recovery => {} } + }); +} + +async fn publish(state: &crate::ServerState) -> Result<(), Status> { + let Some(authority) = &state.sandbox_session_jwt_authority else { + return Ok(()); + }; + let (gateway_id, verification_keys) = authority.preparation_trust()?; + let mut pools = Vec::new(); + let mut offset = 0; + loop { + let templates = state + .store + .list_all_messages::(100, offset) + .await + .map_err(|_| Status::unavailable("pool template inventory unavailable"))?; + let count = templates.len(); + for template in templates { + let Some(metadata) = template.metadata.as_ref() else { + continue; + }; + let Some(startup) = template + .spec + .as_ref() + .and_then(|s| s.desired_service_level.as_ref()) + .and_then(|s| s.startup.as_ref()) + else { + continue; + }; + if metadata.deletion_time.is_some() + || startup.ready_within.is_none() + || startup.max_burst == 0 + { + continue; + } + let spec = crate::grpc::sandbox::sandbox_spec_from_stored_workload_template(&template)?; + pools.push(WarmPoolTarget { + template_id: metadata.id.clone(), + template_name: metadata.name.clone(), + workspace: metadata.workspace.clone(), + target: startup.max_burst, + spec: Some( + driver_sandbox_spec_from_public(&spec, state.compute.configured_driver_name()) + .map_err(|e| *e)?, + ), + }); + } + if count < 100 { + break; + } + offset += 100; + } + let request = SyncWarmPoolsRequest { + pools, + gateway_id, + verification_keys, + }; + state + .compute + .driver + .call( + openshell_otel::rpc::SYNC_WARM_POOLS, + None, + |driver| async move { driver.sync_warm_pools(Request::new(request)).await }, + ) + .await?; + Ok(()) +} + +const CANDIDATE: &str = "internal.openshell.ai/warm-pair-candidate"; +const PENDING: &str = "internal.openshell.ai/warm-pair-pending"; +const ATTACHMENT: &str = "internal.openshell.ai/warm-pair-await-attachment"; +const MAX_ALLOCATION_ATTEMPTS: usize = 4; + +pub(super) fn allocation_claim( + runtime: &super::ComputeRuntime, + sandbox: &openshell_core::proto::Sandbox, +) -> Result, Status> { + let Some(pair) = candidate(sandbox)? else { + return Ok(None); + }; + let attempt = sandbox + .status + .as_ref() + .and_then(|status| status.provisioning.as_ref()) + .ok_or_else(|| Status::internal("missing provisioning attempt"))?; + Ok(Some(crate::persistence::AllocationClaim { + target: serde_json::to_string(&( + runtime.configured_driver_name(), + &pair.namespace, + &pair.uid, + )) + .map_err(|_| Status::internal("encode allocation target"))?, + sandbox_id: sandbox.object_id().into(), + attempt_id: attempt.attempt_id.clone(), + runtime_generation: super::sandbox_runtime_generation(sandbox) + .map_err(Status::internal)? + .into_string(), + })) +} + +pub(super) async fn release_allocation( + runtime: &super::ComputeRuntime, + sandbox: &openshell_core::proto::Sandbox, +) -> Result<(), Status> { + if let Some(claim) = allocation_claim(runtime, sandbox)? { + runtime + .store + .release_allocation(&claim) + .await + .map_err(|_| Status::unavailable("allocation release unavailable"))?; + } + Ok(()) +} + +async fn reserve( + runtime: &super::ComputeRuntime, + sandbox: &mut openshell_core::proto::Sandbox, +) -> Result<(), Status> { + let claim = allocation_claim(runtime, sandbox)?; + let result = runtime + .store + .put_sandbox_allocation( + sandbox, + claim.as_ref(), + crate::persistence::WriteCondition::MatchResourceVersion( + super::sandbox_resource_version(sandbox), + ), + ) + .await + .map_err(|error| match error { + crate::persistence::PersistenceError::AllocationTargetReserved => { + Status::already_exists("allocation target is reserved") + } + crate::persistence::PersistenceError::Conflict { .. } => { + Status::aborted("sandbox changed during allocation") + } + _ => Status::unavailable("allocation reservation unavailable"), + })?; + sandbox + .metadata + .as_mut() + .expect("metadata") + .resource_version = result.resource_version; + runtime.sandbox_index.update_from_sandbox(sandbox); + runtime.sandbox_watch_bus.notify(sandbox.object_id()); + Ok(()) +} + +pub fn candidate( + sandbox: &openshell_core::proto::Sandbox, +) -> Result, Status> { + use prost::Message; + sandbox + .metadata + .as_ref() + .and_then(|m| m.annotations.get(CANDIDATE)) + .map(|value| { + let bytes = + hex::decode(value).map_err(|_| Status::internal("invalid persisted pair"))?; + openshell_core::proto::compute::v1::WarmPairCandidate::decode(bytes.as_slice()) + .map_err(|_| Status::internal("invalid persisted pair")) + }) + .transpose() +} + +pub fn pending(sandbox: &openshell_core::proto::Sandbox) -> bool { + sandbox + .metadata + .as_ref() + .is_some_and(|m| m.annotations.contains_key(PENDING)) +} + +/// Finish claim recovery without discarding the physical assignment or reservation. +/// A restart must persist this together with its new provisioning attempt. +pub(super) fn clear_pending(sandbox: &mut openshell_core::proto::Sandbox) { + if let Some(metadata) = sandbox.metadata.as_mut() { + metadata.annotations.remove(PENDING); + } +} + +pub(super) fn has_allocation(sandbox: &openshell_core::proto::Sandbox) -> bool { + sandbox.metadata.as_ref().is_some_and(|metadata| { + [CANDIDATE, PENDING, ATTACHMENT] + .iter() + .any(|key| metadata.annotations.contains_key(*key)) + }) +} + +pub(super) fn clear_allocation(sandbox: &mut openshell_core::proto::Sandbox) { + if let Some(metadata) = sandbox.metadata.as_mut() { + for key in [CANDIDATE, PENDING, ATTACHMENT] { + metadata.annotations.remove(key); + } + } +} + +/// Retire an unassigned candidate through the driver's claim CAS. If the claim +/// won, normal `StopSandbox` reclaims its compute while retaining restartable storage. +pub(super) async fn retire_pending_allocation( + runtime: &super::ComputeRuntime, + sandbox: &openshell_core::proto::Sandbox, +) -> Result<(), Status> { + let Some(pair) = candidate(sandbox)? else { + return Ok(()); + }; + if let Some(claim) = allocation_claim(runtime, sandbox)? + && runtime + .store + .allocation_conflicts(&claim) + .await + .map_err(|_| Status::unavailable("allocation ownership unavailable"))? + { + return Ok(()); + } + let request = openshell_core::proto::compute::v1::DeleteSandboxRequest { + sandbox_id: sandbox.object_id().into(), + name: sandbox.object_name().into(), + warm_pair: Some(pair), + only_unassigned_pair: true, + }; + match runtime + .driver + .call( + openshell_otel::rpc::DELETE_SANDBOX, + Some(sandbox.object_id()), + |driver| async move { driver.delete_sandbox(Request::new(request)).await }, + ) + .await + { + Ok(_) => Ok(()), + // The candidate is already owned by this sandbox. Do not delete its storage. + Err(error) if error.code() == Code::FailedPrecondition => Ok(()), + Err(error) => Err(error), + } +} + +fn same_allocation( + current: &openshell_core::proto::Sandbox, + expected: &openshell_core::proto::Sandbox, +) -> Result { + fn attempt(sandbox: &openshell_core::proto::Sandbox) -> Option<&str> { + sandbox + .status + .as_ref() + .and_then(|status| status.provisioning.as_ref()) + .map(|record| record.attempt_id.as_str()) + } + Ok(attempt(current) == attempt(expected) + && super::sandbox_runtime_generation(current).map_err(Status::internal)? + == super::sandbox_runtime_generation(expected).map_err(Status::internal)? + && candidate(current)? == candidate(expected)?) +} + +pub async fn prepare( + state: &crate::ServerState, + sandbox: &mut openshell_core::proto::Sandbox, + identity: &mut crate::auth::sandbox_session::PersistedSandboxIdentity, + attachment: bool, +) -> Result<(), Status> { + if let Some(metadata) = &sandbox.metadata { + validate_user_annotations(&metadata.annotations)?; + } + if state.sandbox_session_jwt_authority.is_none() { + return Ok(()); + } + if let Some(pair) = select(state, sandbox, &[]).await? { + bind_candidate(sandbox, identity, Some(pair))?; + sandbox + .metadata + .as_mut() + .expect("validated metadata") + .annotations + .insert(ATTACHMENT.into(), attachment.to_string()); + } + Ok(()) +} + +async fn select( + state: &crate::ServerState, + sandbox: &openshell_core::proto::Sandbox, + excluded: &[String], +) -> Result, Status> { + use openshell_core::ObjectWorkspace; + let Some(provenance) = &sandbox.created_from_workload_template else { + return Ok(None); + }; + let Some(template) = state + .store + .get_message_by_name::( + sandbox.object_workspace(), + &provenance.name, + ) + .await + .map_err(|_| Status::unavailable("template lookup unavailable"))? + else { + return Ok(None); + }; + let Some(metadata) = &template.metadata else { + return Ok(None); + }; + if metadata.deletion_time.is_some() + || metadata.resource_version.to_string() != provenance.resource_version + { + return Ok(None); + } + let request = openshell_core::proto::compute::v1::SelectWarmPairRequest { + excluded_candidate_uids: excluded.to_vec(), + template_id: metadata.id.clone(), + sandbox: Some( + super::driver_sandbox_from_public(sandbox, state.compute.configured_driver_name()) + .map_err(|e| *e)?, + ), + }; + match state + .compute + .driver + .call( + openshell_otel::rpc::SELECT_WARM_PAIR, + None, + |driver| async move { driver.select_warm_pair(Request::new(request)).await }, + ) + .await + { + Ok(response) => Ok(response.into_inner().candidate), + Err(error) if error.code() == Code::Unimplemented => Ok(None), + // Selection has no side effects; cold fallback is safe if discovery fails. + Err(error) => { + warn!(%error, "warm pair discovery unavailable; using cold creation"); + Ok(None) + } + } +} + +pub(super) fn bind_candidate( + sandbox: &mut openshell_core::proto::Sandbox, + identity: &mut crate::auth::sandbox_session::PersistedSandboxIdentity, + pair: Option, +) -> Result<(), Status> { + use prost::Message; + let annotations = &mut sandbox + .metadata + .as_mut() + .ok_or_else(|| Status::internal("missing metadata"))? + .annotations; + if let Some(pair) = pair { + if pair.uid.is_empty() || pair.name.is_empty() || pair.namespace.is_empty() { + return Err(Status::internal("incomplete warm candidate")); + } + identity.runtime_generation = + openshell_core::sandbox_generation::SandboxGenerationId::parse( + pair.runtime_generation.clone(), + ) + .map_err(|_| Status::internal("invalid prepared generation"))?; + annotations.insert(CANDIDATE.into(), hex::encode(pair.encode_to_vec())); + } else { + annotations.remove(CANDIDATE); + } + identity.write(annotations); + annotations.insert(PENDING.into(), "true".into()); + Ok(()) +} + +/// CAS preserves watch updates, deletion, refresh lineage, and other replicas' intents. +async fn save( + runtime: &super::ComputeRuntime, + sandbox: &mut openshell_core::proto::Sandbox, +) -> Result<(), Status> { + use openshell_core::{ObjectLabels, ObjectWorkspace}; + use prost::Message; + let metadata = sandbox + .metadata + .as_ref() + .ok_or_else(|| Status::internal("missing metadata"))?; + let labels = serde_json::to_string(&sandbox.object_labels()) + .map_err(|_| Status::internal("encode labels"))?; + let result = runtime + .store + .put_if( + openshell_core::proto::Sandbox::object_type(), + sandbox.object_id(), + sandbox.object_name(), + sandbox.object_workspace(), + &sandbox.encode_to_vec(), + Some(&labels), + crate::persistence::WriteCondition::MatchResourceVersion(metadata.resource_version), + ) + .await + .map_err(|_| Status::aborted("sandbox changed during warm allocation"))?; + sandbox + .metadata + .as_mut() + .expect("metadata") + .resource_version = result.resource_version; + runtime.sandbox_index.update_from_sandbox(sandbox); + runtime.sandbox_watch_bus.notify(sandbox.object_id()); + Ok(()) +} + +pub async fn completed( + runtime: &super::ComputeRuntime, + expected: &openshell_core::proto::Sandbox, +) -> Result<(), Status> { + // The driver has durably claimed the pair. Wake local waiters even if the + // bookkeeping update below needs recovery. Registration revalidates identity. + runtime.assignment_notifications.send_replace(()); + let sandbox_id = expected.object_id(); + for _ in 0..4 { + let _global = runtime.sync_lock.lock().await; + let Some(mut sandbox) = runtime + .store + .get_message::(sandbox_id) + .await + .map_err(|_| Status::unavailable("sandbox store unavailable"))? + else { + return Ok(()); + }; + if !same_allocation(&sandbox, expected)? { + return Ok(()); + } + if !pending(&sandbox) + || sandbox.phase() == openshell_core::proto::SandboxPhase::Deleting as i32 + || sandbox + .metadata + .as_ref() + .is_some_and(|m| m.deletion_time.is_some()) + { + return Ok(()); + } + sandbox = Box::pin( + runtime.refresh_provisioning_deadline(sandbox, openshell_core::time::now_ms()), + ) + .await + .map_err(Status::unavailable)?; + if super::provisioning_deadline::timed_out(&sandbox) { + return Ok(()); + } + clear_pending(&mut sandbox); + match save(runtime, &mut sandbox).await { + Ok(()) => return Ok(()), + Err(e) if e.code() == Code::Aborted => {} + Err(e) => return Err(e), + } + } + Err(Status::aborted("allocation completion contended")) +} + +async fn recover(state: &crate::ServerState) -> Result<(), Status> { + let mut offset = 0; + loop { + let sandboxes = state + .store + .list_all_messages::(100, offset) + .await + .map_err(|_| Status::unavailable("allocation inventory unavailable"))?; + let count = sandboxes.len(); + for sandbox in sandboxes { + if !(pending(&sandbox) + || super::provisioning_deadline::cleanup_pending(&sandbox) + || (candidate(&sandbox)?.is_some() + && sandbox.phase() == openshell_core::proto::SandboxPhase::Deleting as i32)) + { + continue; + } + match tokio::time::timeout( + Duration::from_secs(30), + recover_one(state, sandbox.object_id()), + ) + .await + { + Ok(Ok(())) => {} + Ok(Err(error)) => { + warn!(%error, sandbox_id = sandbox.object_id(), "warm allocation will retry"); + } + Err(_) => warn!( + sandbox_id = sandbox.object_id(), + "warm allocation timed out; retaining durable intent" + ), + } + } + if count < 100 { + break; + } + offset += 100; + } + Ok(()) +} + +/// Re-read durable intent after every driver operation. A retry never follows a +/// different attempt, generation, or candidate installed by another worker. +async fn reload( + state: &crate::ServerState, + guard: &super::SandboxLifecycleGuard, + id: &str, + expected: Option<&openshell_core::proto::Sandbox>, +) -> Result, Status> { + let _global = state.compute.lock_global_for_lifecycle(guard).await; + let Some(current) = state + .store + .get_message::(id) + .await + .map_err(|_| Status::unavailable("sandbox store unavailable"))? + else { + return Ok(None); + }; + if let Some(expected) = expected + && !same_allocation(¤t, expected)? + { + return Ok(None); + } + Box::pin( + state + .compute + .refresh_provisioning_deadline(current, openshell_core::time::now_ms()), + ) + .await + .map(Some) + .map_err(Status::unavailable) +} + +async fn finish_if_inactive( + state: &crate::ServerState, + sandbox: &openshell_core::proto::Sandbox, + guard: &super::SandboxLifecycleGuard, +) -> Result { + use openshell_core::proto::{SandboxPhase, compute::v1::DeleteSandboxRequest}; + let id = sandbox.object_id(); + if sandbox.phase() == SandboxPhase::Deleting as i32 + || sandbox + .metadata + .as_ref() + .is_some_and(|m| m.deletion_time.is_some()) + { + let request = DeleteSandboxRequest { + sandbox_id: id.into(), + name: sandbox.object_name().into(), + warm_pair: candidate(sandbox)?, + ..Default::default() + }; + state + .compute + .driver + .call( + openshell_otel::rpc::DELETE_SANDBOX, + Some(id), + |driver| async move { driver.delete_sandbox(Request::new(request)).await }, + ) + .await?; + if state + .compute + .get_driver_sandbox(id, sandbox.object_name()) + .await + .map_err(Status::unavailable)? + .is_none() + && !state + .compute + .remove_deleting_sandbox_record(guard, id) + .await + { + return Err(Status::unavailable( + "pair was deleted; logical cleanup will retry", + )); + } + return Ok(true); + } + if super::provisioning_deadline::timed_out(sandbox) { + state + .compute + .reclaim_provisioning_timeout(sandbox, guard) + .await + .map_err(Status::unavailable)?; + return Ok(true); + } + if !pending(sandbox) { + return Ok(true); + } + if matches!( + SandboxPhase::try_from(sandbox.phase()), + Ok(SandboxPhase::Ready | SandboxPhase::Completed) + ) || super::is_failed_main_process_result(sandbox) + { + // A short-lived workload can finish before the claim is acknowledged. + // Its process result proves activation just as readiness does. + completed(&state.compute, sandbox).await?; + return Ok(true); + } + Ok(!matches!( + SandboxPhase::try_from(sandbox.phase()), + Ok(SandboxPhase::Provisioning | SandboxPhase::Starting) + )) +} + +fn allocation_request( + state: &crate::ServerState, + sandbox: &openshell_core::proto::Sandbox, +) -> Result { + use crate::auth::sandbox_session::PersistedSandboxIdentity; + let metadata = sandbox + .metadata + .as_ref() + .ok_or_else(|| Status::internal("missing metadata"))?; + let identity = PersistedSandboxIdentity::read(&metadata.annotations) + .map_err(|_| Status::internal("missing runtime identity"))?; + let authority = state + .sandbox_session_jwt_authority + .as_ref() + .ok_or_else(|| Status::unavailable("session authority unavailable"))?; + let authentication = authority.mint_persisted_launch(sandbox.object_id(), &identity)?; + let mut driver_sandbox = + super::driver_sandbox_from_public(sandbox, state.compute.configured_driver_name()) + .map_err(|e| *e)?; + if let Some(spec) = driver_sandbox.spec.as_mut() { + spec.launch_authentication = serde_json::to_vec(&authentication) + .map_err(|_| Status::internal("encode authentication"))?; + spec.sandbox_token = authentication + .supervisor + .gateway_token + .expose_secret() + .to_string(); + spec.await_main_process_attachment = metadata + .annotations + .get(ATTACHMENT) + .is_some_and(|v| v == "true"); + } + Ok(openshell_core::proto::compute::v1::CreateSandboxRequest { + sandbox: Some(driver_sandbox), + warm_pair: candidate(sandbox)?, + }) +} + +struct AllocationRetry { + remaining: usize, + until: tokio::time::Instant, + excluded: Vec, +} + +impl AllocationRetry { + fn new() -> Self { + Self { + remaining: MAX_ALLOCATION_ATTEMPTS, + until: tokio::time::Instant::now() + Duration::from_secs(2), + excluded: Vec::new(), + } + } + + fn reject(&mut self, pair: &openshell_core::proto::compute::v1::WarmPairCandidate) { + if !self.excluded.contains(&pair.uid) { + self.excluded.push(pair.uid.clone()); + } + } + + async fn replacement( + &self, + state: &crate::ServerState, + sandbox: &openshell_core::proto::Sandbox, + ) -> Result, Status> { + if self.remaining == 0 || tokio::time::Instant::now() >= self.until { + return Ok(None); + } + let pair = match tokio::time::timeout_at(self.until, select(state, sandbox, &self.excluded)) + .await + { + Ok(result) => result?, + Err(_) => return Ok(None), + }; + // Older drivers may ignore the additive exclusion field. + Ok(pair.filter(|pair| !self.excluded.contains(&pair.uid))) + } +} + +/// Initial creation uses the same reservation and immediate recovery path as a +/// restarted gateway. Failed target reservations never reach the driver. +pub async fn create( + state: &crate::ServerState, + mut sandbox: openshell_core::proto::Sandbox, + mut sandbox_sync_guard: Option>, +) -> Result { + use crate::auth::sandbox_session::PersistedSandboxIdentity; + let mut retry = AllocationRetry::new(); + loop { + let pair = candidate(&sandbox)?; + if pair.is_some() { + retry.remaining = retry.remaining.saturating_sub(1); + } + let request = allocation_request(state, &sandbox)?; + let spec = request + .sandbox + .and_then(|sandbox| sandbox.spec) + .ok_or_else(|| Status::internal("missing driver sandbox spec"))?; + match state + .compute + .create_sandbox_authenticated_with_guard( + sandbox.clone(), + Some(spec.sandbox_token), + Some(spec.launch_authentication), + spec.await_main_process_attachment, + &mut sandbox_sync_guard, + ) + .await + { + Ok(_) => break, + Err(error) if pair.is_some() && error.code() == Code::Aborted => { + // The atomic insert rolled back: this candidate belongs to someone else. + retry.reject(pair.as_ref().expect("warm candidate")); + let next = retry.replacement(state, &sandbox).await?; + let mut identity = PersistedSandboxIdentity::new() + .map_err(|_| Status::internal("new runtime identity"))?; + bind_candidate(&mut sandbox, &mut identity, next)?; + } + Err(error) => return Err(error), + } + } + let id = sandbox.object_id(); + // Uncertain outcomes retain the exact persisted target. Repeating that claim + // is idempotent; switching targets still requires a definite rejection. + match tokio::time::timeout( + Duration::from_secs(30), + recover_with_retry(state, id, &mut retry), + ) + .await + { + Ok(Ok(())) => {} + Ok(Err(error)) => { + warn!(%error, sandbox_id = id, "allocation remains pending reconciliation"); + } + Err(_) => warn!( + sandbox_id = id, + "allocation timed out; retaining durable intent" + ), + } + state + .store + .get_message(id) + .await + .map_err(|_| Status::unavailable("sandbox store unavailable"))? + .ok_or_else(|| Status::not_found("sandbox removed during allocation")) +} + +pub(super) async fn recover_one(state: &crate::ServerState, id: &str) -> Result<(), Status> { + recover_with_retry(state, id, &mut AllocationRetry::new()).await +} + +async fn recover_with_retry( + state: &crate::ServerState, + id: &str, + retry: &mut AllocationRetry, +) -> Result<(), Status> { + use crate::auth::sandbox_session::PersistedSandboxIdentity; + use openshell_core::proto::compute::v1::DeleteSandboxRequest; + let guard = state.compute.lifecycle_gates.lock_for(id).await; + let mut expected = None; + let mut replace = false; + loop { + let Some(mut sandbox) = reload(state, &guard, id, expected.as_ref()).await? else { + return Ok(()); + }; + if Box::pin(finish_if_inactive(state, &sandbox, &guard)).await? { + return Ok(()); + } + expected = Some(sandbox.clone()); + if replace { + let next = retry.replacement(state, &sandbox).await?; + let Some(current) = reload(state, &guard, id, Some(&sandbox)).await? else { + return Ok(()); + }; + if Box::pin(finish_if_inactive(state, ¤t, &guard)).await? { + return Ok(()); + } + sandbox = current; + let mut identity = PersistedSandboxIdentity::new() + .map_err(|_| Status::internal("new runtime identity"))?; + bind_candidate(&mut sandbox, &mut identity, next)?; + } + let pair = candidate(&sandbox)?; + if pair.is_some() { + retry.remaining = retry.remaining.saturating_sub(1); + } + match reserve(&state.compute, &mut sandbox).await { + Ok(()) => {} + Err(error) if pair.is_some() && error.code() == Code::AlreadyExists => { + retry.reject(pair.as_ref().expect("warm candidate")); + // No ownership was acquired. Do not retire the winner's target. + replace = true; + continue; + } + Err(error) => return Err(error), + } + expected = Some(sandbox.clone()); + let request = allocation_request(state, &sandbox)?; + let result = state + .compute + .driver + .call( + openshell_otel::rpc::CREATE_SANDBOX, + Some(id), + |driver| async move { driver.create_sandbox(Request::new(request)).await }, + ) + .await; + let Some(current) = reload(state, &guard, id, Some(&sandbox)).await? else { + return Ok(()); + }; + if Box::pin(finish_if_inactive(state, ¤t, &guard)).await? { + return Ok(()); + } + match result { + Ok(_) => return completed(&state.compute, ¤t).await, + Err(error) if pair.is_none() && error.code() == Code::AlreadyExists => { + return completed(&state.compute, ¤t).await; + } + Err(error) if pair.is_some() && error.code() == Code::Aborted => { + let pair = pair.expect("warm candidate"); + let request = DeleteSandboxRequest { + sandbox_id: id.into(), + name: current.object_name().into(), + warm_pair: Some(pair.clone()), + only_unassigned_pair: true, + }; + match state + .compute + .driver + .call( + openshell_otel::rpc::DELETE_SANDBOX, + Some(id), + |driver| async move { driver.delete_sandbox(Request::new(request)).await }, + ) + .await + { + Ok(_) => {} + // Another replica claimed this same intent. Retry its idempotent claim. + Err(error) if error.code() == Code::FailedPrecondition => { + if retry.remaining == 0 { + return Err(error); + } + replace = false; + continue; + } + Err(error) => return Err(error), + } + release_allocation(&state.compute, ¤t).await?; + retry.reject(&pair); + replace = true; + } + Err(error) => return Err(error), + } + } +} +pub fn validate_user_annotations( + annotations: &std::collections::HashMap, +) -> Result<(), Status> { + if annotations + .keys() + .any(|key| [CANDIDATE, PENDING, ATTACHMENT].contains(&key.as_str())) + { + return Err(Status::invalid_argument( + "warm pair annotations are gateway-owned", + )); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + #[tokio::test] + async fn a_new_gateway_recovers_the_persisted_pair_and_deletes_it_without_repooling() { + use crate::{ + Config, ServerState, + auth::{ + sandbox_jwt::SandboxSessionJwtAuthority, sandbox_session::PersistedSandboxIdentity, + }, + persistence::Store, + }; + use openshell_core::proto::{ + Sandbox, SandboxPhase, compute::v1::WarmPairCandidate, datamodel::v1::ObjectMeta, + }; + let store = Arc::new(Store::connect("sqlite::memory:").await.unwrap()); + let mut sandbox = Sandbox { + metadata: Some(ObjectMeta { + id: "logical".into(), + name: "warm".into(), + workspace: "default".into(), + ..Default::default() + }), + ..Default::default() + }; + sandbox.set_phase(SandboxPhase::Provisioning as i32); + sandbox.status.as_mut().unwrap().provisioning = Some( + super::super::provisioning_deadline::new_record(openshell_core::time::now_ms()), + ); + let mut identity = PersistedSandboxIdentity::new().unwrap(); + let pair = WarmPairCandidate { + namespace: "openshell".into(), + name: "physical".into(), + uid: "parent-uid".into(), + runtime_generation: "prepared-generation".into(), + ..Default::default() + }; + bind_candidate(&mut sandbox, &mut identity, Some(pair.clone())).unwrap(); + store.put_message(&sandbox).await.unwrap(); + // Construct fresh gateway memory after the candidate was durably written. + let runtime = super::super::new_test_runtime(store.clone()).await; + let mut state = ServerState::new( + Config::new(None).with_credential_drivers(["test-static"]), + store.clone(), + runtime.clone(), + runtime.sandbox_index.clone(), + runtime.sandbox_watch_bus.clone(), + runtime.tracing_log_bus.clone(), + runtime.supervisor_sessions.clone(), + None, + ); + let key = openshell_bootstrap::jwt::generate_jwt_key().unwrap(); + state.sandbox_session_jwt_authority = Some(Arc::new( + SandboxSessionJwtAuthority::from_pem( + key.signing_key_pem.as_bytes(), + key.public_key_pem.as_bytes(), + key.kid, + "gateway", + Duration::from_hours(1), + ) + .unwrap(), + )); + let mut previous_allocation = sandbox.clone(); + previous_allocation + .metadata + .as_mut() + .unwrap() + .annotations + .remove(CANDIDATE); + completed(&state.compute, &previous_allocation) + .await + .unwrap(); + assert!( + pending( + &store + .get_message::("logical") + .await + .unwrap() + .unwrap() + ), + "a stale completion must not clear another candidate" + ); + recover_one(&state, "logical").await.unwrap(); + let mut recovered = store + .get_message::("logical") + .await + .unwrap() + .unwrap(); + assert_eq!(candidate(&recovered).unwrap(), Some(pair)); + assert!(!pending(&recovered)); + assert_eq!( + PersistedSandboxIdentity::read(&recovered.metadata.as_ref().unwrap().annotations) + .unwrap(), + identity + ); + recovered.set_phase(SandboxPhase::Deleting as i32); + store.put_message(&recovered).await.unwrap(); + recover_one(&state, "logical").await.unwrap(); + assert!( + store + .get_message::("logical") + .await + .unwrap() + .is_none() + ); + } + + #[test] + fn warm_allocation_annotations_cannot_be_changed_through_public_updates() { + for key in [CANDIDATE, PENDING, ATTACHMENT] { + assert_eq!( + validate_user_annotations(&[(key.into(), "forged".into())].into()) + .unwrap_err() + .code(), + Code::InvalidArgument + ); + } + assert!( + validate_user_annotations(&[("example.com/user".into(), "value".into())].into()) + .is_ok() + ); + } +} diff --git a/crates/openshell-server/src/grpc/mod.rs b/crates/openshell-server/src/grpc/mod.rs index 5202c9fd8f..8347c7b93a 100644 --- a/crates/openshell-server/src/grpc/mod.rs +++ b/crates/openshell-server/src/grpc/mod.rs @@ -8,7 +8,8 @@ pub mod mutation_replay; pub mod policy; pub mod provider; pub mod provider_readiness; -mod sandbox; +pub mod sandbox; +mod supervisor_registration; pub use sandbox::mint_persisted_authentication; mod service; mod validation; @@ -758,6 +759,13 @@ impl OpenShell for OpenShellService { // --- Sandbox identity --- + async fn register_supervisor( + &self, + request: Request, + ) -> Result, Status> { + supervisor_registration::register(&self.state, request).await + } + async fn issue_sandbox_token( &self, request: Request, diff --git a/crates/openshell-server/src/grpc/policy.rs b/crates/openshell-server/src/grpc/policy.rs index c9863813e3..8bc86446aa 100644 --- a/crates/openshell-server/src/grpc/policy.rs +++ b/crates/openshell-server/src/grpc/policy.rs @@ -3538,6 +3538,7 @@ async fn handle_update_config_inner( let replay_facts = super::mutation_replay::ordinary::Facts::from_request(&request); let req = request.into_inner(); validate_annotations(&req.annotations, "annotations")?; + crate::compute::warm_pool::validate_user_annotations(&req.annotations)?; let sandbox = if req.global { if !req.sandbox.is_empty() || req.workspace_scope.is_some() { return Err(Status::invalid_argument( diff --git a/crates/openshell-server/src/grpc/sandbox.rs b/crates/openshell-server/src/grpc/sandbox.rs index a12fdadb9a..0da00eec94 100644 --- a/crates/openshell-server/src/grpc/sandbox.rs +++ b/crates/openshell-server/src/grpc/sandbox.rs @@ -450,7 +450,7 @@ async fn handle_create_sandbox_inner( .authorize(&token, &workspace, &subject)?; } - let _sandbox_sync_guard = if spec.providers.is_empty() { + let mut sandbox_sync_guard = if spec.providers.is_empty() { None } else { Some(state.compute.sandbox_sync_guard().await) @@ -588,8 +588,15 @@ async fn handle_create_sandbox_inner( status })?; - let runtime_identity = crate::auth::sandbox_session::PersistedSandboxIdentity::new() + let mut runtime_identity = crate::auth::sandbox_session::PersistedSandboxIdentity::new() .map_err(|error| Status::internal(error.to_string()))?; + crate::compute::warm_pool::prepare( + state, + &mut sandbox, + &mut runtime_identity, + await_main_process_attachment, + ) + .await?; if let Some(metadata) = sandbox.metadata.as_mut() { runtime_identity.write(&mut metadata.annotations); } @@ -618,15 +625,25 @@ async fn handle_create_sandbox_inner( }) .transpose()?; - let sandbox = state - .compute - .create_sandbox_authenticated( + let sandbox = if crate::compute::warm_pool::pending(&sandbox) { + Box::pin(crate::compute::warm_pool::create( + state, sandbox, - sandbox_token, - launch_authentication, - await_main_process_attachment, - ) - .await?; + sandbox_sync_guard, + )) + .await? + } else { + state + .compute + .create_sandbox_authenticated_with_guard( + sandbox, + sandbox_token, + launch_authentication, + await_main_process_attachment, + &mut sandbox_sync_guard, + ) + .await? + }; let mut service_urls = HashMap::with_capacity(request.service_exposures.len()); for exposure in &request.service_exposures { @@ -748,7 +765,7 @@ fn validate_template_create_governance_spec(spec: &SandboxSpec) -> Result<(), St Ok(()) } -fn sandbox_spec_from_stored_workload_template( +pub fn sandbox_spec_from_stored_workload_template( template: &SandboxWorkloadTemplate, ) -> Result { sandbox_spec_from_workload_template(template, tonic::Code::Internal) diff --git a/crates/openshell-server/src/grpc/supervisor_registration.rs b/crates/openshell-server/src/grpc/supervisor_registration.rs new file mode 100644 index 0000000000..5878193d34 --- /dev/null +++ b/crates/openshell-server/src/grpc/supervisor_registration.rs @@ -0,0 +1,277 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Initial assignment over a driver-authenticated registration connection. + +use crate::{ + ServerState, + auth::{ + principal::{Principal, SandboxIdentitySource}, + sandbox_session::PersistedSandboxIdentity, + }, +}; +use openshell_core::proto::{ + RegisterSupervisorRequest, RegisterSupervisorResponse, Sandbox, SandboxPhase, +}; +use std::{sync::Arc, time::Duration}; +use tonic::{Request, Response, Status}; + +pub(super) async fn register( + state: &Arc, + request: Request, +) -> Result, Status> { + let Principal::Sandbox(principal) = super::extract_principal(&request)? else { + return Err(Status::permission_denied( + "registration requires a driver-authenticated proxy", + )); + }; + let SandboxIdentitySource::SupervisorRegistration { + registration: expected, + } = principal.source + else { + return Err(Status::permission_denied( + "operational credentials cannot register a proxy", + )); + }; + let credential = request + .metadata() + .get("authorization") + .and_then(|v| v.to_str().ok()) + .and_then(|v| v.strip_prefix("Bearer ")) + .ok_or_else(|| Status::unauthenticated("missing registration credential"))?; + + // A bounded wait lets clients reread projected credentials and reconnect. + // Durable state is authoritative; no assignment depends on this handler's lifetime. + // Subscribe before reading assignment so a claim during the read cannot be + // lost. Notifications carry no authority; every wake repeats authentication. + let mut assignments = state.compute.subscribe_assignments(); + let deadline = tokio::time::Instant::now() + Duration::from_secs(30); + while tokio::time::Instant::now() < deadline { + let observed = state.compute.authenticate_supervisor(credential).await?; + let registration = observed + .registration + .ok_or_else(|| Status::permission_denied("missing registration identity"))?; + if registration.instance_id != expected.instance_id + || registration.sandbox_resource_uid != expected.sandbox_resource_uid + || registration.workload_pod_uid != expected.workload_pod_uid + || registration.runtime_generation != expected.runtime_generation + || registration.session_id != expected.session_id + || registration.resource_binding != expected.resource_binding + { + return Err(Status::permission_denied( + "prepared runtime changed during registration", + )); + } + if !observed.sandbox_id.is_empty() + && let Some(sandbox) = state + .store + .get_message::(&observed.sandbox_id) + .await + .map_err(|_| Status::unavailable("sandbox persistence unavailable"))? + { + let authority = state + .sandbox_session_jwt_authority + .as_ref() + .ok_or_else(|| Status::unavailable("session signing unavailable"))?; + return assignment_response(authority, &observed.sandbox_id, &sandbox, registration) + .map(Response::new); + } + tokio::select! { + _ = assignments.changed() => {}, + // Other gateway replicas and crash recovery still use durable state. + () = tokio::time::sleep(Duration::from_secs(1)) => {}, + () = tokio::time::sleep_until(deadline) => break, + } + } + Err(Status::unavailable("proxy is still awaiting assignment")) +} + +fn assignment_response( + authority: &crate::auth::sandbox_jwt::SandboxSessionJwtAuthority, + sandbox_id: &str, + sandbox: &Sandbox, + registration: openshell_core::proto::compute::v1::SupervisorRegistration, +) -> Result { + let phase = SandboxPhase::try_from(sandbox.phase()).unwrap_or(SandboxPhase::Unknown); + if !matches!( + phase, + SandboxPhase::Provisioning + | SandboxPhase::Starting + | SandboxPhase::Ready + | SandboxPhase::Completed + ) { + return Err(Status::failed_precondition( + "sandbox does not permit activation", + )); + } + let metadata = sandbox + .metadata + .as_ref() + .ok_or_else(|| Status::internal("missing sandbox metadata"))?; + let identity = PersistedSandboxIdentity::read(&metadata.annotations) + .map_err(|_| Status::failed_precondition("missing runtime identity"))?; + if metadata.id != sandbox_id || metadata.deletion_time.is_some() { + return Err(Status::failed_precondition( + "sandbox assignment is not active", + )); + } + if crate::compute::warm_pool::candidate(sandbox)? + .is_some_and(|pair| pair.uid != registration.sandbox_resource_uid) + { + return Err(Status::permission_denied( + "proxy does not belong to persisted pair", + )); + } + if registration.runtime_generation != identity.runtime_generation.as_str() { + return Err(Status::failed_precondition( + "prepared generation does not match persisted assignment", + )); + } + if registration.resource_binding.is_empty() { + return Err(Status::permission_denied( + "missing driver registration binding", + )); + } + let session_id = registration + .session_id + .parse() + .map_err(|_| Status::internal("invalid prepared session identity"))?; + let bundle = authority.mint_registration( + sandbox_id, + &identity, + session_id, + registration.resource_binding.into_iter().collect(), + )?; + Ok(RegisterSupervisorResponse { + sandbox_id: sandbox_id.to_string(), + sandbox_name: metadata.name.clone(), + backend_descriptor: registration.backend_descriptor, + auth_bundle: serde_json::to_vec(&bundle) + .map_err(|_| Status::internal("encode credentials"))?, + main_process_spec: registration.main_process_spec, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::auth::{ + sandbox_jwt::SandboxSessionJwtAuthority, sandbox_session::RefreshRequestHash, + }; + use openshell_core::proto::compute::v1::WarmPairCandidate; + use openshell_core::{ + SandboxSessionId, + jwt::{ + SessionJwtVerifier, SessionTokenProfile, SessionVerificationKey, SupervisorAuthBundle, + SystemJwtClock, + }, + proto::{compute::v1::SupervisorRegistration, datamodel::v1::ObjectMeta}, + }; + use prost::Message; + + #[test] + fn registration_retries_preserve_assignment_and_cannot_revive_refreshed_credentials() { + let key = openshell_bootstrap::jwt::generate_jwt_key().unwrap(); + let authority = SandboxSessionJwtAuthority::from_pem( + key.signing_key_pem.as_bytes(), + key.public_key_pem.as_bytes(), + key.kid.clone(), + "gateway", + Duration::from_hours(1), + ) + .unwrap(); + let verifier = SessionJwtVerifier::new( + "gateway", + SessionTokenProfile::Sandbox, + [SessionVerificationKey { + key_id: key.kid, + public_key_pem: key.public_key_pem.into_bytes(), + }], + Arc::new(SystemJwtClock), + ) + .unwrap(); + let identity = PersistedSandboxIdentity::new().unwrap(); + let mut sandbox = Sandbox { + metadata: Some(ObjectMeta { + id: "sandbox-a".into(), + name: "example".into(), + ..Default::default() + }), + ..Default::default() + }; + identity.write(&mut sandbox.metadata.as_mut().unwrap().annotations); + sandbox.set_phase(SandboxPhase::Provisioning as i32); + let registration = SupervisorRegistration { + runtime_generation: identity.runtime_generation.to_string(), + session_id: SandboxSessionId::new().to_string(), + resource_binding: [("workload-uid".into(), "pod-a".into())].into(), + ..Default::default() + }; + let first = + assignment_response(&authority, "sandbox-a", &sandbox, registration.clone()).unwrap(); + let second = + assignment_response(&authority, "sandbox-a", &sandbox, registration.clone()).unwrap(); + let first: SupervisorAuthBundle = serde_json::from_slice(&first.auth_bundle).unwrap(); + let second: SupervisorAuthBundle = serde_json::from_slice(&second.auth_bundle).unwrap(); + assert_eq!(first.session_id, second.session_id); + assert_eq!(first.runtime_generation, second.runtime_generation); + assert_eq!( + authority + .verify_gateway_token(first.gateway_token.expose_secret()) + .unwrap() + .token_id, + authority + .verify_gateway_token(second.gateway_token.expose_secret()) + .unwrap() + .token_id + ); + let signed = verifier + .verify(first.sandbox_token.expose_secret()) + .unwrap(); + assert_eq!(signed.resource_binding["workload-uid"], "pod-a"); + assert_eq!(signed.sandbox_id.as_str(), "sandbox-a"); + + let mut warm_registration = registration.clone(); + warm_registration.sandbox_resource_uid = "prepared-parent".into(); + let pair = WarmPairCandidate { + uid: "prepared-parent".into(), + ..Default::default() + }; + sandbox.metadata.as_mut().unwrap().annotations.insert( + "internal.openshell.ai/warm-pair-candidate".into(), + hex::encode(pair.encode_to_vec()), + ); + assert!(assignment_response(&authority, "sandbox-a", &sandbox, warm_registration).is_ok()); + assert_eq!( + assignment_response(&authority, "sandbox-a", &sandbox, registration.clone()) + .unwrap_err() + .code(), + tonic::Code::PermissionDenied + ); + sandbox + .metadata + .as_mut() + .unwrap() + .annotations + .remove("internal.openshell.ai/warm-pair-candidate"); + + let mut wrong = registration.clone(); + wrong.runtime_generation = "other-generation".into(); + assert!(assignment_response(&authority, "sandbox-a", &sandbox, wrong).is_err()); + let mut wrong = registration.clone(); + wrong.resource_binding.clear(); + assert!(assignment_response(&authority, "sandbox-a", &sandbox, wrong).is_err()); + sandbox.set_phase(SandboxPhase::Stopped as i32); + assert!( + assignment_response(&authority, "sandbox-a", &sandbox, registration.clone()).is_err() + ); + sandbox.set_phase(SandboxPhase::Ready as i32); + let refreshed = identity.next_gateway_token( + RefreshRequestHash::from_extension_services(&[]), + 1_900_000_000, + 60, + ); + refreshed.write(&mut sandbox.metadata.as_mut().unwrap().annotations); + assert!(assignment_response(&authority, "sandbox-a", &sandbox, registration).is_err()); + } +} diff --git a/crates/openshell-server/src/lib.rs b/crates/openshell-server/src/lib.rs index a8cd09acca..38f595dfac 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -839,6 +839,7 @@ pub(crate) async fn run_server( } startup_tx.send_replace(true); + compute::warm_pool::spawn(state.clone(), shutdown_rx.clone()); ssh_sessions::spawn_session_reaper(store.clone(), Duration::from_hours(1)); supervisor_session::spawn_relay_reaper(state.clone(), Duration::from_secs(30)); provider_refresh::spawn_refresh_worker(state.clone(), Duration::from_mins(1)); diff --git a/crates/openshell-server/src/multiplex.rs b/crates/openshell-server/src/multiplex.rs index d50af3175b..b4e48bf59b 100644 --- a/crates/openshell-server/src/multiplex.rs +++ b/crates/openshell-server/src/multiplex.rs @@ -668,6 +668,9 @@ fn gateway_principal_fields(principal: &Principal) -> BTreeMap { SandboxIdentitySource::BootstrapJwt { .. } => "bootstrap_jwt", SandboxIdentitySource::BootstrapCert { .. } => "bootstrap_cert", SandboxIdentitySource::ComputeDriver { .. } => "compute_driver", + SandboxIdentitySource::SupervisorRegistration { .. } => { + "supervisor_registration" + } } .to_string(), ); diff --git a/crates/openshell-server/src/persistence/allocation.rs b/crates/openshell-server/src/persistence/allocation.rs new file mode 100644 index 0000000000..f6c774e96a --- /dev/null +++ b/crates/openshell-server/src/persistence/allocation.rs @@ -0,0 +1,257 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +use super::{PersistenceResult, Store, WriteCondition, WriteResult}; +use openshell_core::proto::Sandbox; + +/// A physical target and the exact logical attempt entitled to claim it. +/// Reservations have no TTL: an uncertain driver call may still be in flight. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AllocationClaim { + pub target: String, + pub sandbox_id: String, + pub attempt_id: String, + pub runtime_generation: String, +} + +impl Store { + pub(crate) async fn allocation_conflicts( + &self, + claim: &AllocationClaim, + ) -> PersistenceResult { + match self { + Self::Sqlite(store) => store.allocation_conflicts(claim).await, + Self::Postgres(store) => store.allocation_conflicts(claim).await, + } + } + /// Commit sandbox intent and exclusive target ownership in one transaction. + /// Any target or sandbox CAS conflict rolls back both writes. + #[tracing::instrument(name = "store", skip_all, fields(otel.name = "store.put_sandbox_allocation", otel.status_code = tracing::field::Empty))] + pub(crate) async fn put_sandbox_allocation( + &self, + sandbox: &Sandbox, + claim: Option<&AllocationClaim>, + condition: WriteCondition, + ) -> PersistenceResult { + use openshell_core::ObjectId; + if claim.is_some_and(|claim| claim.sandbox_id != sandbox.object_id()) { + return Err(super::PersistenceError::Config( + "allocation owner must match sandbox".into(), + )); + } + let result = match self { + Self::Sqlite(store) => { + store + .put_sandbox_allocation(sandbox, claim, condition) + .await + } + Self::Postgres(store) => { + store + .put_sandbox_allocation(sandbox, claim, condition) + .await + } + }; + if let Err(error) = &result + && !error.is_expected() + { + crate::otel_tracing::mark_error(&tracing::Span::current()); + } + result + } + + /// Only call after the driver has fenced or reclaimed this owner's target. + pub(crate) async fn release_allocation( + &self, + claim: &AllocationClaim, + ) -> PersistenceResult<()> { + match self { + Self::Sqlite(store) => store.release_allocation(claim).await, + Self::Postgres(store) => store.release_allocation(claim).await, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::persistence::PersistenceError; + use openshell_core::{ObjectId, proto::ObjectMeta}; + + fn intent(id: &str, target: &str) -> (Sandbox, AllocationClaim) { + let sandbox = Sandbox { + metadata: Some(ObjectMeta { + id: id.into(), + name: id.into(), + workspace: "default".into(), + ..Default::default() + }), + ..Default::default() + }; + let claim = AllocationClaim { + target: target.into(), + sandbox_id: id.into(), + attempt_id: "attempt".into(), + runtime_generation: "generation".into(), + }; + (sandbox, claim) + } + + async fn check_atomic_reservations(store: &Store) { + let prefix = uuid::Uuid::new_v4().to_string(); + let (first, owner) = intent(&format!("{prefix}-first"), &format!("{prefix}-target")); + let (second, contender) = intent(&format!("{prefix}-second"), &owner.target); + let inserted = store + .put_sandbox_allocation(&first, Some(&owner), WriteCondition::MustCreate) + .await + .unwrap(); + assert!(matches!( + store + .put_sandbox_allocation(&second, Some(&contender), WriteCondition::MustCreate) + .await, + Err(PersistenceError::AllocationTargetReserved) + )); + assert!( + store + .get_message::(second.object_id()) + .await + .unwrap() + .is_none(), + "losing create must roll back its sandbox row" + ); + + let updated = store + .put_sandbox_allocation( + &first, + Some(&owner), + WriteCondition::MatchResourceVersion(inserted.resource_version), + ) + .await + .unwrap(); + let mut wrong_attempt = owner.clone(); + wrong_attempt.attempt_id = "new-attempt".into(); + assert!(matches!( + store + .put_sandbox_allocation( + &first, + Some(&wrong_attempt), + WriteCondition::MatchResourceVersion(updated.resource_version) + ) + .await, + Err(PersistenceError::AllocationTargetReserved) + )); + let current = store + .get_message::(first.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!( + current.metadata.as_ref().unwrap().resource_version, + updated.resource_version, + "failed reservation must roll back the sandbox update" + ); + store.release_allocation(&wrong_attempt).await.unwrap(); + assert!( + store.allocation_conflicts(&contender).await.unwrap(), + "a stale attempt cannot release ownership" + ); + + let mut unused = owner.clone(); + unused.target = format!("{prefix}-unused"); + assert!(matches!( + store + .put_sandbox_allocation( + &first, + Some(&unused), + WriteCondition::MatchResourceVersion(inserted.resource_version) + ) + .await, + Err(PersistenceError::Conflict { .. }) + )); + unused.sandbox_id.clone_from(&contender.sandbox_id); + assert!( + !store.allocation_conflicts(&unused).await.unwrap(), + "failed sandbox CAS cannot reserve a target" + ); + + store.release_allocation(&owner).await.unwrap(); + store + .put_sandbox_allocation(&second, Some(&contender), WriteCondition::MustCreate) + .await + .unwrap(); + store.delete("sandbox", second.object_id()).await.unwrap(); + assert!( + !store.allocation_conflicts(&owner).await.unwrap(), + "logical deletion must remove its reservation" + ); + store.delete("sandbox", first.object_id()).await.unwrap(); + } + + #[tokio::test] + async fn allocation_reservations_are_atomic_and_owner_fenced() { + check_atomic_reservations(&crate::persistence::test_store().await).await; + } + + #[tokio::test] + async fn allocation_reservation_has_one_winner_across_connections_and_restart() { + let dir = tempfile::tempdir().unwrap(); + let url = format!("sqlite://{}", dir.path().join("allocation.db").display()); + let left = Store::connect(&url).await.unwrap(); + let right = Store::connect(&url).await.unwrap(); + let (first, owner) = intent("first", "same-target"); + let (second, contender) = intent("second", "same-target"); + let (a, b) = tokio::join!( + left.put_sandbox_allocation(&first, Some(&owner), WriteCondition::MustCreate), + right.put_sandbox_allocation(&second, Some(&contender), WriteCondition::MustCreate), + ); + assert_ne!(a.is_ok(), b.is_ok()); + let losing_claim = if a.is_ok() { &contender } else { &owner }; + assert!(matches!( + if a.is_ok() { b } else { a }, + Err(PersistenceError::AllocationTargetReserved) + )); + left.close_for_test().await; + right.close_for_test().await; + let restarted = Store::connect(&url).await.unwrap(); + assert!(restarted.allocation_conflicts(losing_claim).await.unwrap()); + assert!( + restarted + .get_message::(&losing_claim.sandbox_id) + .await + .unwrap() + .is_none() + ); + } + + #[tokio::test] + #[ignore = "requires OPENSHELL_TEST_POSTGRES_URL pointing to a disposable PostgreSQL database"] + async fn allocation_reservations_postgres() { + let url = std::env::var("OPENSHELL_TEST_POSTGRES_URL").unwrap(); + let left = Store::connect(&url).await.unwrap(); + check_atomic_reservations(&left).await; + let right = Store::connect(&url).await.unwrap(); + let target = uuid::Uuid::new_v4().to_string(); + let (first, owner) = intent(&format!("{target}-left"), &target); + let (second, contender) = intent(&format!("{target}-right"), &target); + let (a, b) = tokio::join!( + left.put_sandbox_allocation(&first, Some(&owner), WriteCondition::MustCreate), + right.put_sandbox_allocation(&second, Some(&contender), WriteCondition::MustCreate), + ); + assert_ne!(a.is_ok(), b.is_ok()); + let losing_claim = if a.is_ok() { &contender } else { &owner }; + assert!(matches!( + if a.is_ok() { b } else { a }, + Err(PersistenceError::AllocationTargetReserved) + )); + let restarted = Store::connect(&url).await.unwrap(); + assert!(restarted.allocation_conflicts(losing_claim).await.unwrap()); + assert!( + restarted + .get_message::(&losing_claim.sandbox_id) + .await + .unwrap() + .is_none() + ); + left.delete("sandbox", first.object_id()).await.unwrap(); + left.delete("sandbox", second.object_id()).await.unwrap(); + } +} diff --git a/crates/openshell-server/src/persistence/mod.rs b/crates/openshell-server/src/persistence/mod.rs index 0b365ccd0c..cb70f971c7 100644 --- a/crates/openshell-server/src/persistence/mod.rs +++ b/crates/openshell-server/src/persistence/mod.rs @@ -3,7 +3,9 @@ //! Persistence layer for `OpenShell` Server. +mod allocation; mod legacy_time_wire; +pub use allocation::AllocationClaim; mod postgres; mod sqlite; @@ -36,6 +38,8 @@ pub const DELETE_MANY_BATCH_SIZE: usize = 128; /// Persistence-layer error type. #[derive(Debug, Error, Clone)] pub enum PersistenceError { + #[error("allocation target is reserved by another attempt")] + AllocationTargetReserved, #[error("configuration error: {0}")] Config(String), #[error("database error: {0}")] @@ -67,7 +71,10 @@ impl PersistenceError { /// a race is how [`crate::compute::lease`] learns the lease is held, and a /// version conflict is what drives an optimistic-concurrency retry. pub fn is_expected(&self) -> bool { - matches!(self, Self::UniqueViolation { .. } | Self::Conflict { .. }) + matches!( + self, + Self::UniqueViolation { .. } | Self::Conflict { .. } | Self::AllocationTargetReserved + ) } pub fn unique_violation(constraint: Option, detail: Option) -> Self { diff --git a/crates/openshell-server/src/persistence/postgres.rs b/crates/openshell-server/src/persistence/postgres.rs index c0be94e0c9..1e8687c790 100644 --- a/crates/openshell-server/src/persistence/postgres.rs +++ b/crates/openshell-server/src/persistence/postgres.rs @@ -35,6 +35,93 @@ pub struct PostgresStore { } impl PostgresStore { + pub(crate) async fn allocation_conflicts( + &self, + claim: &super::AllocationClaim, + ) -> PersistenceResult { + let owner = sqlx::query("SELECT sandbox_id, attempt_id, runtime_generation FROM allocation_claims WHERE target = $1") + .bind(&claim.target).fetch_optional(&self.pool).await.map_err(|e| map_db_error(&e))?; + Ok(owner.is_some_and(|owner| { + owner.get::("sandbox_id") != claim.sandbox_id + || owner.get::("attempt_id") != claim.attempt_id + || owner.get::("runtime_generation") != claim.runtime_generation + })) + } + + pub(crate) async fn put_sandbox_allocation( + &self, + sandbox: &Sandbox, + claim: Option<&super::AllocationClaim>, + condition: WriteCondition, + ) -> PersistenceResult { + use openshell_core::{ObjectId, ObjectLabels, ObjectName, ObjectWorkspace}; + let now_ms = current_time_ms(); + let labels = serde_json::to_value(sandbox.object_labels().unwrap_or_default()) + .map_err(|error| PersistenceError::Encode(error.to_string()))?; + let payload = sandbox.encode_to_vec(); + let mut tx = self.pool.begin().await.map_err(|e| map_db_error(&e))?; + let row = match condition { + WriteCondition::MustCreate => sqlx::query( + "INSERT INTO objects (object_type, id, name, workspace, payload, labels, created_at_ms, updated_at_ms, resource_version) + VALUES ('sandbox', $1, $2, $3, $4, $5, $6, $6, 1) + RETURNING resource_version, created_at_ms, updated_at_ms" + ) + .bind(sandbox.object_id()).bind(sandbox.object_name()).bind(sandbox.object_workspace()) + .bind(&payload).bind(&labels).bind(now_ms) + .fetch_optional(&mut *tx).await.map_err(|e| map_db_error(&e))?, + WriteCondition::MatchResourceVersion(version) => sqlx::query( + "UPDATE objects SET payload = $1, labels = $2, updated_at_ms = $3, resource_version = resource_version + 1 + WHERE object_type = 'sandbox' AND id = $4 AND resource_version = $5 + RETURNING resource_version, created_at_ms, updated_at_ms" + ) + .bind(&payload).bind(&labels).bind(now_ms).bind(sandbox.object_id()) + .bind(i64::try_from(version).unwrap_or(i64::MAX)) + .fetch_optional(&mut *tx).await.map_err(|e| map_db_error(&e))?, + WriteCondition::Unconditional => return Err(PersistenceError::Config("allocation intent requires a conditional write".into())), + }.ok_or(PersistenceError::Conflict { current_resource_version: None })?; + if let Some(claim) = claim { + sqlx::query( + "INSERT INTO allocation_claims (target, sandbox_id, attempt_id, runtime_generation) + VALUES ($1, $2, $3, $4) ON CONFLICT (target) DO NOTHING", + ) + .bind(&claim.target) + .bind(&claim.sandbox_id) + .bind(&claim.attempt_id) + .bind(&claim.runtime_generation) + .execute(&mut *tx) + .await + .map_err(|e| map_db_error(&e))?; + let owner = sqlx::query("SELECT sandbox_id, attempt_id, runtime_generation FROM allocation_claims WHERE target = $1") + .bind(&claim.target).fetch_one(&mut *tx).await.map_err(|e| map_db_error(&e))?; + if owner.get::("sandbox_id") != claim.sandbox_id + || owner.get::("attempt_id") != claim.attempt_id + || owner.get::("runtime_generation") != claim.runtime_generation + { + return Err(PersistenceError::AllocationTargetReserved); + } + } + let version: i64 = row.get("resource_version"); + let result = WriteResult { + resource_version: version.max(1).cast_unsigned(), + created_at_ms: row.get("created_at_ms"), + updated_at_ms: row.get("updated_at_ms"), + }; + tx.commit().await.map_err(|e| map_db_error(&e))?; + Ok(result) + } + + pub(crate) async fn release_allocation( + &self, + claim: &super::AllocationClaim, + ) -> PersistenceResult<()> { + sqlx::query( + "DELETE FROM allocation_claims WHERE target = $1 AND sandbox_id = $2 AND attempt_id = $3 AND runtime_generation = $4" + ) + .bind(&claim.target).bind(&claim.sandbox_id).bind(&claim.attempt_id).bind(&claim.runtime_generation) + .execute(&self.pool).await.map_err(|e| map_db_error(&e))?; + Ok(()) + } + pub async fn connect(url: &str) -> PersistenceResult { let pool = PgPoolOptions::new() .max_connections(10) diff --git a/crates/openshell-server/src/persistence/sqlite.rs b/crates/openshell-server/src/persistence/sqlite.rs index d9f22bcbbe..b939aa920f 100644 --- a/crates/openshell-server/src/persistence/sqlite.rs +++ b/crates/openshell-server/src/persistence/sqlite.rs @@ -71,6 +71,93 @@ pub(super) async fn replace_pool_connection(store: &SqliteStore) -> PersistenceR } impl SqliteStore { + pub(crate) async fn allocation_conflicts( + &self, + claim: &super::AllocationClaim, + ) -> PersistenceResult { + let owner = sqlx::query("SELECT sandbox_id, attempt_id, runtime_generation FROM allocation_claims WHERE target = ?1") + .bind(&claim.target).fetch_optional(&self.pool).await.map_err(|e| map_db_error(&e))?; + Ok(owner.is_some_and(|owner| { + owner.get::("sandbox_id") != claim.sandbox_id + || owner.get::("attempt_id") != claim.attempt_id + || owner.get::("runtime_generation") != claim.runtime_generation + })) + } + + pub(crate) async fn put_sandbox_allocation( + &self, + sandbox: &Sandbox, + claim: Option<&super::AllocationClaim>, + condition: WriteCondition, + ) -> PersistenceResult { + use openshell_core::{ObjectId, ObjectLabels, ObjectName, ObjectWorkspace}; + let now_ms = current_time_ms(); + let labels = serde_json::to_string(&sandbox.object_labels().unwrap_or_default()) + .map_err(|error| PersistenceError::Encode(error.to_string()))?; + let payload = sandbox.encode_to_vec(); + let mut tx = self.pool.begin().await.map_err(|e| map_db_error(&e))?; + let row = match condition { + WriteCondition::MustCreate => sqlx::query( + "INSERT INTO objects (object_type, id, name, workspace, payload, labels, created_at_ms, updated_at_ms, resource_version) + VALUES ('sandbox', ?1, ?2, ?3, ?4, ?5, ?6, ?6, 1) + RETURNING resource_version, created_at_ms, updated_at_ms" + ) + .bind(sandbox.object_id()).bind(sandbox.object_name()).bind(sandbox.object_workspace()) + .bind(&payload).bind(&labels).bind(now_ms) + .fetch_optional(&mut *tx).await.map_err(|e| map_db_error(&e))?, + WriteCondition::MatchResourceVersion(version) => sqlx::query( + "UPDATE objects SET payload = ?1, labels = ?2, updated_at_ms = ?3, resource_version = resource_version + 1 + WHERE object_type = 'sandbox' AND id = ?4 AND resource_version = ?5 + RETURNING resource_version, created_at_ms, updated_at_ms" + ) + .bind(&payload).bind(&labels).bind(now_ms).bind(sandbox.object_id()) + .bind(i64::try_from(version).unwrap_or(i64::MAX)) + .fetch_optional(&mut *tx).await.map_err(|e| map_db_error(&e))?, + WriteCondition::Unconditional => return Err(PersistenceError::Config("allocation intent requires a conditional write".into())), + }.ok_or(PersistenceError::Conflict { current_resource_version: None })?; + if let Some(claim) = claim { + sqlx::query( + "INSERT INTO allocation_claims (target, sandbox_id, attempt_id, runtime_generation) + VALUES (?1, ?2, ?3, ?4) ON CONFLICT (target) DO NOTHING", + ) + .bind(&claim.target) + .bind(&claim.sandbox_id) + .bind(&claim.attempt_id) + .bind(&claim.runtime_generation) + .execute(&mut *tx) + .await + .map_err(|e| map_db_error(&e))?; + let owner = sqlx::query("SELECT sandbox_id, attempt_id, runtime_generation FROM allocation_claims WHERE target = ?1") + .bind(&claim.target).fetch_one(&mut *tx).await.map_err(|e| map_db_error(&e))?; + if owner.get::("sandbox_id") != claim.sandbox_id + || owner.get::("attempt_id") != claim.attempt_id + || owner.get::("runtime_generation") != claim.runtime_generation + { + return Err(PersistenceError::AllocationTargetReserved); + } + } + let version: i64 = row.get("resource_version"); + let result = WriteResult { + resource_version: version.max(1).cast_unsigned(), + created_at_ms: row.get("created_at_ms"), + updated_at_ms: row.get("updated_at_ms"), + }; + tx.commit().await.map_err(|e| map_db_error(&e))?; + Ok(result) + } + + pub(crate) async fn release_allocation( + &self, + claim: &super::AllocationClaim, + ) -> PersistenceResult<()> { + sqlx::query( + "DELETE FROM allocation_claims WHERE target = ?1 AND sandbox_id = ?2 AND attempt_id = ?3 AND runtime_generation = ?4" + ) + .bind(&claim.target).bind(&claim.sandbox_id).bind(&claim.attempt_id).bind(&claim.runtime_generation) + .execute(&self.pool).await.map_err(|e| map_db_error(&e))?; + Ok(()) + } + /// Closes the connection pool. #[cfg(test)] pub(crate) async fn close_for_test(&self) { diff --git a/crates/openshell-server/src/storage_proto.rs b/crates/openshell-server/src/storage_proto.rs index 5c47d29a08..357d632a02 100644 --- a/crates/openshell-server/src/storage_proto.rs +++ b/crates/openshell-server/src/storage_proto.rs @@ -119,7 +119,7 @@ mod tests { const STORAGE_V1_SCHEMA_SHA256: &str = "d68401809d8cea445c35233ef32412bbd041cb2ac5acaf368a0d0bf74d2ddf17"; const PUBLIC_RPC_SCHEMA_SHA256: &str = - "71d3c60feedf0d6a9d15a3884dfb0cdc277a9309b55d3b94f519065df2b19cb2"; + "6627f2dccb38d7ea35b6c74a00f1320d66dd1e471c119d7fb471bd8a3b062314"; const DURABLE_SCHEMA_SHA256: &str = "9eeaa29dfba187bff69fb7bc4f9a13a0f1d7be3f7049a38c8f0e20ce77ec7d8b"; const PUBLIC_DURABLE_OVERLAP_SHA256: &str = @@ -518,12 +518,12 @@ mod tests { } assert_eq!( compiled_method_count, - 101 + PROVIDER_READINESS_RPC_SIGNATURES.len(), + 104 + PROVIDER_READINESS_RPC_SIGNATURES.len(), "classify every compiled RPC" ); assert_eq!( methods.len(), - 76 + PROVIDER_READINESS_RPC_SIGNATURES.len(), + 77 + PROVIDER_READINESS_RPC_SIGNATURES.len(), "inventory every public gateway RPC" ); assert_eq!( @@ -531,7 +531,7 @@ mod tests { .iter() .filter(|method| method.starts_with("openshell.v1.OpenShell/")) .count(), - 76 + PROVIDER_READINESS_RPC_SIGNATURES.len() + 77 + PROVIDER_READINESS_RPC_SIGNATURES.len() ); assert!(methods.iter().all(|method| !method.contains(".storage."))); @@ -575,7 +575,7 @@ mod tests { overlap_hash.as_str(), ), ( - (302, 25), + (304, 25), (92, 19), (80, 19), PUBLIC_RPC_SCHEMA_SHA256, diff --git a/crates/openshell-server/src/test_support.rs b/crates/openshell-server/src/test_support.rs index 9d9650c2f0..3a9a188db7 100644 --- a/crates/openshell-server/src/test_support.rs +++ b/crates/openshell-server/src/test_support.rs @@ -275,6 +275,23 @@ impl Stream for UnixIncoming { #[tonic::async_trait] impl ComputeDriver for FakeComputeDriver { + async fn select_warm_pair( + &self, + request: Request, + ) -> Result, Status> { + let _ = &request; + Err(Status::unimplemented( + "warm pair allocation is not supported", + )) + } + + async fn sync_warm_pools( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented("warm pools are not supported")) + } + async fn authenticate_sandbox( &self, _request: Request, diff --git a/crates/openshell-server/tests/common/mod.rs b/crates/openshell-server/tests/common/mod.rs index 64e2762e2a..446f174329 100644 --- a/crates/openshell-server/tests/common/mod.rs +++ b/crates/openshell-server/tests/common/mod.rs @@ -554,6 +554,13 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("not implemented in test")) } + async fn register_supervisor( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("not implemented in test")) + } + async fn issue_sandbox_token( &self, _request: tonic::Request, diff --git a/crates/openshell-server/tests/supervisor_relay_integration.rs b/crates/openshell-server/tests/supervisor_relay_integration.rs index 31ab9cb1eb..5162aa5ce0 100644 --- a/crates/openshell-server/tests/supervisor_relay_integration.rs +++ b/crates/openshell-server/tests/supervisor_relay_integration.rs @@ -513,6 +513,13 @@ impl OpenShell for RelayGateway { ) -> Result, Status> { Err(Status::unimplemented("unused")) } + async fn register_supervisor( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("not implemented in test")) + } + async fn issue_sandbox_token( &self, _: tonic::Request, diff --git a/crates/openshell-supervisor/src/main.rs b/crates/openshell-supervisor/src/main.rs index 515572b083..da191b3277 100644 --- a/crates/openshell-supervisor/src/main.rs +++ b/crates/openshell-supervisor/src/main.rs @@ -113,6 +113,10 @@ struct Args { #[arg(long)] auth_bundle_file: Option, + /// Wait for gateway assignment using the projected proxy identity. + #[arg(long, conflicts_with = "auth_bundle_file")] + register_supervisor: bool, + /// Loopback HTTP/CONNECT listener used by `--role=network-proxy`. #[arg(long)] listen: Option, @@ -210,9 +214,9 @@ fn validate_role_arguments(args: &Args) -> Result<()> { "--backend-descriptor-file is required for --role=isolation-backend" )); } - if args.auth_bundle_file.is_none() { + if args.auth_bundle_file.is_none() && !args.register_supervisor { return Err(miette::miette!( - "--auth-bundle-file is required for --role=isolation-backend" + "--auth-bundle-file or --register-supervisor is required for --role=isolation-backend" )); } if args.listen.is_some() { @@ -229,6 +233,7 @@ fn validate_role_arguments(args: &Args) -> Result<()> { SupervisorRole::NetworkProxy => { if args.backend_descriptor_file.is_some() || args.auth_bundle_file.is_some() + || args.register_supervisor || args.sandbox_id.is_some() || args.sandbox.is_some() || args.openshell_endpoint.is_some() @@ -289,13 +294,64 @@ fn main() -> Result<()> { return openshell_supervisor::check_control_readiness(&args.socket); } - let args = Args::parse(); + let mut args = Args::parse(); validate_role_arguments(&args)?; arm_parent_liveness(args.parent_liveness_fd)?; validate_main_exit_marker(args.main_exit_marker.as_deref())?; + let assignment = if args.register_supervisor { + let endpoint = args + .openshell_endpoint + .as_deref() + .ok_or_else(|| miette::miette!("registration requires a gateway endpoint"))?; + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .into_diagnostic()?; + let bootstrap_logging = tracing_subscriber::fmt() + .with_env_filter( + EnvFilter::try_from_default_env() + .unwrap_or_else(|_| EnvFilter::new(&args.log_level)), + ) + .with_writer(std::io::stderr) + .finish(); + let assignment = tracing::subscriber::with_default(bootstrap_logging, || { + info!("Waiting for gateway runtime assignment"); + runtime.block_on(openshell_core::grpc_client::register_supervisor(endpoint)) + })?; + args.sandbox_id = Some(assignment.sandbox_id.clone()); + args.sandbox = Some(assignment.sandbox_name.clone()); + Some(assignment) + } else { + None + }; let isolation_inputs = if args.role == SupervisorRole::IsolationBackend { - let descriptor = backend_descriptor(&args)?; - let auth = auth_bundle(&args)?; + let (descriptor, auth) = if let Some(assignment) = &assignment { + let prepared = backend_descriptor(&args)?; + let prepared: openshell_sandbox_backend::boundary_protocol::SandboxRuntimeDescriptor = + serde_json::from_slice(&prepared.payload).into_diagnostic()?; + let assigned: openshell_sandbox_backend::boundary_protocol::SandboxRuntimeDescriptor = + serde_json::from_slice(&assignment.backend_descriptor).into_diagnostic()?; + let mut expected = prepared; + expected.boundary_id.clone_from(&assignment.sandbox_id); + if expected != assigned { + return Err(miette::miette!( + "assignment does not match the prepared runtime" + )); + } + let auth: openshell_core::jwt::SupervisorAuthBundle = + serde_json::from_slice(&assignment.auth_bundle).into_diagnostic()?; + auth.validate().into_diagnostic()?; + if auth.session_id != assigned.session_id + || auth.runtime_generation.as_str() != assigned.generation + { + return Err(miette::miette!( + "assignment credentials do not match prepared generation" + )); + } + (assigned.backend_descriptor().into_diagnostic()?, auth) + } else { + (backend_descriptor(&args)?, auth_bundle(&args)?) + }; // Install the driver-provisioned session before starting log push or // any other gateway client. `run_sandbox` obtains the same Sandbox // Protocol bearer slot after validating the descriptor binding. @@ -388,7 +444,11 @@ fn main() -> Result<()> { let workdir = args.workdir.clone(); let (command, interactive, await_main_process_attachment) = if !args.command.is_empty() { (args.command, args.interactive, false) - } else if let Ok(json) = std::env::var(openshell_core::sandbox_env::MAIN_PROCESS_SPEC) { + } else if let Some(json) = assignment + .as_ref() + .map(|a| a.main_process_spec.clone()) + .or_else(|| std::env::var(openshell_core::sandbox_env::MAIN_PROCESS_SPEC).ok()) + { let config = openshell_core::sandbox_env::MainProcessConfig::decode(&json) .map_err(|error| miette::miette!("{error}"))?; ( @@ -423,7 +483,7 @@ fn main() -> Result<()> { }; let admitted_isolation_backend = std::env::var(openshell_core::sandbox_env::ADMITTED_ISOLATION_BACKEND).ok(); - openshell_supervisor::run_sandbox( + Box::pin(openshell_supervisor::run_sandbox( command, workdir, args.timeout, @@ -442,7 +502,7 @@ fn main() -> Result<()> { auth_bundle, admitted_isolation_backend, args.main_exit_marker, - ) + )) .await } SupervisorRole::NetworkProxy => { @@ -505,6 +565,35 @@ mod tests { assert!(validate_role_arguments(&args).is_err()); } + #[test] + fn registration_replaces_the_auth_file_only_for_isolation_backends() { + let args = Args::try_parse_from([ + "openshell-supervisor", + "--backend-descriptor-file", + "/tmp/runtime.json", + "--register-supervisor", + ]) + .unwrap(); + assert!(validate_role_arguments(&args).is_ok()); + assert!( + Args::try_parse_from([ + "openshell-supervisor", + "--register-supervisor", + "--auth-bundle-file", + "/tmp/auth.json", + ]) + .is_err() + ); + let args = Args::try_parse_from([ + "openshell-supervisor", + "--role", + "network-proxy", + "--register-supervisor", + ]) + .unwrap(); + assert!(validate_role_arguments(&args).is_err()); + } + #[test] fn network_proxy_accepts_local_policy_files() { let args = Args::try_parse_from([ diff --git a/deploy/helm/openshell/README.md b/deploy/helm/openshell/README.md index 003475df96..65399fa602 100644 --- a/deploy/helm/openshell/README.md +++ b/deploy/helm/openshell/README.md @@ -264,6 +264,7 @@ discovery endpoint or its TLS CA. | server.disableTls | bool | `false` | Disable TLS entirely - the server listens on plaintext HTTP. Set to true when a reverse proxy / tunnel terminates TLS at the edge. | | server.drivers.kubernetes.operatorNamespaceFile | string | `""` | Path to a JSON file containing an array of namespace names allowed in operator mode. Hot-reloaded on change. | | server.drivers.kubernetes.operatorNamespaceLabel | string | `""` | K8s label selector for namespace discovery in operator mode. The driver watches namespaces matching this label. | +| server.drivers.kubernetes.warmPool | object | `{"enabled":true,"maxIdleSeconds":3600,"maxPairs":10,"preparationTimeoutSeconds":300}` | Prepare and claim single-use sandbox/proxy pairs for templates with startup hints. | | server.drivers.kubernetes.workspaceMode | string | `"shared"` | How workspaces map to Kubernetes namespaces. "shared" (default): all sandboxes in a single namespace. "managed": auto-creates per-workspace namespaces. "operator": uses pre-provisioned namespaces. | | server.enableLoopbackServiceHttp | bool | `true` | Enable plaintext HTTP routing for loopback sandbox service URLs on TLS-enabled gateways. | | server.enableUserNamespaces | bool | `false` | Enable Kubernetes user namespace isolation (hostUsers: false) for sandbox pods. Requires Kubernetes 1.33+ with user namespace support available (beta through 1.35, GA in 1.36+), plus a supporting container runtime and Linux 5.12+. When enabled, container UID 0 maps to an unprivileged host UID and capabilities become namespaced. | diff --git a/deploy/helm/openshell/ci/values-warm-pool-preparation.yaml b/deploy/helm/openshell/ci/values-warm-pool-preparation.yaml new file mode 100644 index 0000000000..6f649394b2 --- /dev/null +++ b/deploy/helm/openshell/ci/values-warm-pool-preparation.yaml @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +server: + drivers: + kubernetes: + warmPool: + enabled: true + maxPairs: 2 + preparationTimeoutSeconds: 240 + maxIdleSeconds: 3600 diff --git a/deploy/helm/openshell/templates/clusterrole.yaml b/deploy/helm/openshell/templates/clusterrole.yaml index e48c27cdd2..b1216940bf 100644 --- a/deploy/helm/openshell/templates/clusterrole.yaml +++ b/deploy/helm/openshell/templates/clusterrole.yaml @@ -44,6 +44,9 @@ rules: - delete {{- end }} {{- if ne $workspaceMode "shared" }} + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["get", "create", "update"] # Cluster-wide sandbox CRD access for managed/operator workspace modes. - apiGroups: - agents.x-k8s.io @@ -95,9 +98,8 @@ rules: verbs: ["create", "get"] - apiGroups: [""] resources: ["secrets"] - # Bootstrap Secrets are generation-scoped. Recovery lists them by label and - # deletes stale generations with UID preconditions before retrying creation. - verbs: ["create", "list", "delete"] + # Registration reads bootstrap material; stop/rollback lists and deletes it. + verbs: ["create", "get", "list", "delete"] - apiGroups: ["networking.k8s.io"] resources: ["networkpolicies"] verbs: ["create", "get"] diff --git a/deploy/helm/openshell/templates/gateway-config.yaml b/deploy/helm/openshell/templates/gateway-config.yaml index 0652db4b07..3958365da4 100644 --- a/deploy/helm/openshell/templates/gateway-config.yaml +++ b/deploy/helm/openshell/templates/gateway-config.yaml @@ -220,6 +220,12 @@ data: gateway_namespace = {{ .Release.Namespace | quote }} gateway_pod_selector = { "app.kubernetes.io/name" = {{ include "openshell.name" . | quote }}, "app.kubernetes.io/instance" = {{ .Release.Name | quote }} } + [openshell.drivers.kubernetes.warm_pool] + enabled = {{ .Values.server.drivers.kubernetes.warmPool.enabled }} + max_pairs = {{ .Values.server.drivers.kubernetes.warmPool.maxPairs }} + preparation_timeout_seconds = {{ .Values.server.drivers.kubernetes.warmPool.preparationTimeoutSeconds }} + max_idle_seconds = {{ .Values.server.drivers.kubernetes.warmPool.maxIdleSeconds }} + [openshell.drivers.kubernetes.sandbox_runtime] network_policy_enforced = {{ .Values.supervisor.sandboxRuntime.networkPolicyEnforced }} boundary_port = {{ .Values.supervisor.sandboxRuntime.boundaryPort | default 5500 }} diff --git a/deploy/helm/openshell/templates/role.yaml b/deploy/helm/openshell/templates/role.yaml index c781b46e3d..72bb1ae847 100644 --- a/deploy/helm/openshell/templates/role.yaml +++ b/deploy/helm/openshell/templates/role.yaml @@ -10,6 +10,9 @@ metadata: labels: {{- include "openshell.labels" . | nindent 4 }} rules: + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["get", "create", "update"] - apiGroups: - agents.x-k8s.io resources: @@ -53,9 +56,8 @@ rules: verbs: ["create", "get"] - apiGroups: [""] resources: ["secrets"] - # Bootstrap Secrets are generation-scoped. Recovery lists them by label and - # deletes stale generations with UID preconditions before retrying creation. - verbs: ["create", "list", "delete"] + # Registration reads bootstrap material; stop/rollback lists and deletes it. + verbs: ["create", "get", "list", "delete"] - apiGroups: ["networking.k8s.io"] resources: ["networkpolicies"] verbs: ["create", "get"] diff --git a/deploy/helm/openshell/tests/clusterrole_test.yaml b/deploy/helm/openshell/tests/clusterrole_test.yaml index 9639232c32..d69be5eeb2 100644 --- a/deploy/helm/openshell/tests/clusterrole_test.yaml +++ b/deploy/helm/openshell/tests/clusterrole_test.yaml @@ -69,7 +69,7 @@ tests: resourceNames: ["custom-client-tls"] verbs: ["get", "patch"] - - it: grants operator bootstrap secret lifecycle permissions when TLS and credential storage are disabled + - it: grants operator bootstrap Secret lifecycle access without credential storage set: server.drivers.kubernetes.workspaceMode: operator server.disableTls: true @@ -79,7 +79,7 @@ tests: content: apiGroups: [""] resources: ["secrets"] - verbs: ["create", "list", "delete"] + verbs: ["create", "get", "list", "delete"] - notContains: path: rules content: @@ -107,7 +107,7 @@ tests: content: apiGroups: [""] resources: ["secrets"] - verbs: ["create", "list", "delete"] + verbs: ["create", "get", "list", "delete"] - it: restricts managed copies to image-pull secrets when TLS is disabled set: @@ -128,7 +128,7 @@ tests: content: apiGroups: [""] resources: ["secrets"] - verbs: ["create", "list", "delete"] + verbs: ["create", "get", "list", "delete"] - it: omits secrets rule entirely in shared mode set: @@ -162,7 +162,7 @@ tests: content: apiGroups: [""] resources: ["secrets"] - verbs: ["create", "list", "delete"] + verbs: ["create", "get", "list", "delete"] - it: grants operator sandbox-runtime companion permissions set: @@ -185,4 +185,4 @@ tests: content: apiGroups: [""] resources: ["secrets"] - verbs: ["create", "list", "delete"] + verbs: ["create", "get", "list", "delete"] diff --git a/deploy/helm/openshell/tests/gateway_config_test.yaml b/deploy/helm/openshell/tests/gateway_config_test.yaml index a9186d3339..d675887f2b 100644 --- a/deploy/helm/openshell/tests/gateway_config_test.yaml +++ b/deploy/helm/openshell/tests/gateway_config_test.yaml @@ -928,3 +928,14 @@ tests: - matchRegex: path: data["gateway.toml"] pattern: "(?ms)\\[openshell\\.drivers\\.kubernetes\\].*?image_pull_policy\\s*=\\s*\\\"never\\\".*?supervisor_image_pull_policy\\s*=\\s*\\\"never\\\"" + + - it: enables warm pooling by default with bounded preparation settings + template: templates/gateway-config.yaml + set: + server.drivers.kubernetes.warmPool.maxPairs: 3 + server.drivers.kubernetes.warmPool.preparationTimeoutSeconds: 120 + server.drivers.kubernetes.warmPool.maxIdleSeconds: 900 + asserts: + - matchRegex: + path: data["gateway.toml"] + pattern: '(?ms)\[openshell\.drivers\.kubernetes\.warm_pool\]\s*enabled = true\s*max_pairs = 3\s*preparation_timeout_seconds = 120\s*max_idle_seconds = 900' diff --git a/deploy/helm/openshell/tests/sandbox_namespace_test.yaml b/deploy/helm/openshell/tests/sandbox_namespace_test.yaml index 6ec7ae4d65..d9fcf8aea0 100644 --- a/deploy/helm/openshell/tests/sandbox_namespace_test.yaml +++ b/deploy/helm/openshell/tests/sandbox_namespace_test.yaml @@ -13,6 +13,16 @@ release: namespace: my-namespace tests: + - it: permits registration to read prepared proxy Secrets in shared mode + template: templates/role.yaml + asserts: + - contains: + path: rules + content: + apiGroups: [""] + resources: ["secrets"] + verbs: ["create", "get", "list", "delete"] + - it: defaults the Kubernetes driver namespace to release namespace template: templates/gateway-config.yaml asserts: @@ -73,7 +83,7 @@ tests: content: apiGroups: [""] resources: ["secrets"] - verbs: ["create", "list", "delete"] + verbs: ["create", "get", "list", "delete"] - it: uses explicit sandboxNamespace for sandbox RoleBinding template: templates/rolebinding.yaml diff --git a/deploy/helm/openshell/values.yaml b/deploy/helm/openshell/values.yaml index 67d5587ec9..c3b062903c 100644 --- a/deploy/helm/openshell/values.yaml +++ b/deploy/helm/openshell/values.yaml @@ -266,6 +266,12 @@ server: # "managed": auto-creates per-workspace namespaces. # "operator": uses pre-provisioned namespaces. workspaceMode: "shared" + # -- Prepare and claim single-use sandbox/proxy pairs for templates with startup hints. + warmPool: + enabled: true + maxPairs: 10 + preparationTimeoutSeconds: 300 + maxIdleSeconds: 3600 # -- K8s label selector for namespace discovery in operator mode. # The driver watches namespaces matching this label. operatorNamespaceLabel: "" diff --git a/docs/reference/gateway-auth.mdx b/docs/reference/gateway-auth.mdx index 4d9e0841ad..f87137b26c 100644 --- a/docs/reference/gateway-auth.mdx +++ b/docs/reference/gateway-auth.mdx @@ -222,7 +222,7 @@ Common identity providers such as Keycloak (RS256), Microsoft Entra ID (RSA), an If `OPENSHELL_OIDC_SCOPES_CLAIM` is set, the gateway also enforces scopes. It accepts space-delimited scope strings such as `scope: "openid sandbox:read"` and JSON arrays such as `scp: ["sandbox:read"]`. Standard OIDC scopes such as `openid`, `profile`, `email`, and `offline_access` are ignored for authorization. `openshell:all` grants access to all scoped methods. -Supervisor-to-gateway RPCs do not use user OIDC tokens or mTLS user identity. Each sandbox supervisor presents a gateway-minted `Authorization: Bearer` token scoped to its sandbox ID. On Kubernetes, the Kubernetes compute driver validates the projected ServiceAccount token with TokenReview, verifies the live pod UID and controlling `Sandbox` ownerReference, and returns the authenticated sandbox ID to the gateway. The gateway verifies that sandbox still exists before minting its JWT. Log upload, policy status, provider environment lookup, and sandbox config sync run with sandbox-restricted scope, while CLI users authenticate with OIDC, edge auth, local mTLS user authentication, or an explicitly enabled unauthenticated local developer mode. Provider environment responses expose only the credentials and configuration attached to that sandbox, subject to endpoint binding and credential expiry checks. +Supervisor-to-gateway RPCs do not use user OIDC tokens or mTLS user identity. Each sandbox supervisor presents a gateway-minted `Authorization: Bearer` token scoped to its sandbox ID. On Kubernetes, the supervisor calls `RegisterSupervisor` with a projected ServiceAccount token. The compute driver validates it with TokenReview and verifies the live proxy Pod, workload Pod and owning `Sandbox`. The gateway checks the persisted runtime assignment before returning the operational credentials and launch configuration. The workload accepts its logical sandbox identity only after checking the gateway-signed binding to its prepared runtime. Operational credentials are delivered in memory rather than stored in the proxy bootstrap Secret. A reconnect can retry initial assignment, but registration cannot recover credentials that have already been superseded through refresh. Log upload, policy status, provider environment lookup, and sandbox config sync run with sandbox-restricted scope, while CLI users authenticate with OIDC, edge auth, local mTLS user authentication, or an explicitly enabled unauthenticated local developer mode. Provider environment responses expose only the credentials and configuration attached to that sandbox, subject to endpoint binding and credential expiry checks. Re-authenticate an OIDC gateway with: diff --git a/docs/reference/gateway-config.mdx b/docs/reference/gateway-config.mdx index 58fb6b62a7..ca8736ebba 100644 --- a/docs/reference/gateway-config.mdx +++ b/docs/reference/gateway-config.mdx @@ -629,6 +629,13 @@ enabled = true gateway_namespace = "openshell" gateway_pod_selector = { "app.kubernetes.io/name" = "openshell", "app.kubernetes.io/instance" = "openshell" } +# Optional preparation and allocation of idle workload/proxy pairs. +[openshell.drivers.kubernetes.warm_pool] +enabled = true +max_pairs = 10 +preparation_timeout_seconds = 300 +max_idle_seconds = 3600 + [openshell.drivers.kubernetes.sandbox_runtime] # Required acknowledgement that the cluster CNI enforces NetworkPolicy and the # sandbox namespaces prevent untrusted policy, pod, label, and Secret changes. @@ -637,6 +644,55 @@ network_policy_enforced = true boundary_port = 5500 ``` +Warm pooling is experimental and enabled by default. Disable it in Helm +with `server.drivers.kubernetes.warmPool.enabled=false`, or set `enabled = false` +in `[openshell.drivers.kubernetes.warm_pool]`. Templates with +`startup.ready_within` and a positive `startup.max_burst` request spare pairs; +`max_pairs` caps total unassigned inventory across templates. Preparing pairs +count toward the cap. When the cap is exhausted, some templates may have fewer +spares than requested. Creating a sandbox with `--template` claims a compatible +ready pair from that template. Without a matching spare, creation uses the cold +path. Command, TTY, policy and providers are applied on assignment; the workload +shape comes from the template. An uncertain claim remains pending until the +gateway resolves it. Assigned pairs support ordinary stop/start and are deleted +with their sandbox; they are never returned to the pool. + +In operator workspace mode, templates outside the namespace allowlist are skipped +without interrupting pools in eligible workspaces. Their templates become eligible +again when the namespace returns to the allowlist. Interrupted preparation can +resume after Pod release when the recorded resource identities and bootstrap +journal still match. + +Claimed Pods retain their prepared names. The gateway asynchronously labels both +Pods with the assigned `openshell.ai/sandbox-name` and `openshell.ai/sandbox-id`. +It also updates their `openshell.ai/sandbox-id` annotations so SPIRE uses the +logical sandbox ID. Reconciliation repairs missing or stale labels and annotations, +including after stop/start. Display the mapping with: + +```shell +kubectl -n get pods \ + -L openshell.ai/sandbox-name,openshell.ai/boundary-role +``` + +Select both Pods for a sandbox with `-l openshell.ai/sandbox-name=`. +Idle and claimed warm-pool Pods also carry `openshell.ai/template-name` and +`openshell.ai/template-id`. Add `-L openshell.ai/template-name` to display the +template, or use `-l openshell.ai/template-id=` to select its Pods. +These labels remain after assignment; existing idle pairs are backfilled during +pool reconciliation. +In shared namespaces, sandbox names can repeat across workspaces; use +`-l openshell.ai/sandbox-id=` for an unambiguous lookup. +Assignment wakes waiting supervisors on the same gateway replica immediately; +other replicas discover it through the one-second recovery poll. + +The controller needs `get`, `create`, and `update` permission for Kubernetes +Leases in the driver's `namespace`; the chart grants these permissions. Pool +preparation uses the existing Sandbox, Pod, Service, Secret and NetworkPolicy +permissions. Disabling preparation retires unassigned inventory. Claimed pairs +are not retired by the pool controller. `preparation_timeout_seconds` must be at +least 30; `max_idle_seconds` must be at least that timeout and at most 86400. +`max_pairs` may be zero and must not exceed 1000. + In managed workspace mode, the Kubernetes driver copies each explicitly named `image_pull_secrets` Secret from `namespace` into the managed workspace namespace on sandbox creation. Shared and operator modes require the Secret to diff --git a/docs/sandboxes/manage-sandboxes.mdx b/docs/sandboxes/manage-sandboxes.mdx index 056332c37b..77f000fb74 100644 --- a/docs/sandboxes/manage-sandboxes.mdx +++ b/docs/sandboxes/manage-sandboxes.mdx @@ -846,6 +846,15 @@ retaining the sandbox record, diagnostic, and restartable storage. Cleanup can remain pending if the backend is unavailable; the gateway retries it. Inspect `provisioning` in JSON output for the deadline, timeout, and cleanup timestamps. TUI NOTES distinguishes pending cleanup from reclaimed compute. +Cleanup also retires any pending warm-pool allocation. Background recovery does +not resume an expired provisioning attempt, including after a gateway restart. + +Concurrent creates using a warm pool reserve separate targets. If a target is +already reserved or its claim is rejected, the gateway immediately tries another +candidate within a bounded retry budget, then falls back to cold creation. +An uncertain claim keeps its target for recovery to avoid duplicate allocations. +If the command finishes before claim recovery completes, restarting the sandbox +reuses its assigned pair without retrying the previous allocation. Repair the configuration, wait for cleanup to complete, then explicitly restart: diff --git a/e2e/rust/Cargo.toml b/e2e/rust/Cargo.toml index 333acd0fec..6e887508f3 100644 --- a/e2e/rust/Cargo.toml +++ b/e2e/rust/Cargo.toml @@ -29,6 +29,7 @@ e2e-docker = ["e2e", "e2e-host-gateway", "e2e-local-container-driver"] e2e-gpu = ["e2e"] e2e-docker-gpu = ["e2e-docker", "e2e-gpu"] e2e-kubernetes = ["e2e"] +e2e-kubernetes-warm-pool = ["e2e-kubernetes"] e2e-kubernetes-credential-drivers = ["e2e-kubernetes"] e2e-kubernetes-workspace-managed = ["e2e-kubernetes"] e2e-kubernetes-workspace-operator = ["e2e-kubernetes"] diff --git a/e2e/rust/tests/credential_gating.rs b/e2e/rust/tests/credential_gating.rs index bf0d6ae9b5..1b81b67a06 100644 --- a/e2e/rust/tests/credential_gating.rs +++ b/e2e/rust/tests/credential_gating.rs @@ -550,14 +550,20 @@ with socket.create_connection((host, port), timeout=10) as sock: sock.sendall(request) sock.settimeout(3) response = b"" + timed_out = False while True: try: chunk = sock.recv(4096) except socket.timeout: + timed_out = True break if not chunk: break response += chunk + # Report framing/timing without printing credential-bearing response data. + status_parts = response.split(b"\r\n", 1)[0].split(b" ", 2) + status = status_parts[1].decode("ascii") if len(status_parts) > 1 and status_parts[1].isdigit() else "unknown" + print(f"BODY_RESPONSE status={{status}} bytes={{len(response)}} timed_out={{timed_out}}") print("BODY_REWRITTEN" if b"BODY_REWRITTEN" in response else "BODY_TEXT" if b"BODY_TEXT" in response else "BODY_DENIED") "# ) @@ -842,7 +848,11 @@ async fn run_profile_body_sandbox(port: u16) -> Result { async fn assert_rest_body_preserves_placeholder(server: &HttpProbeServer) -> Result<(), String> { let output = run_profile_body_sandbox(server.port).await?; - assert!(output.contains("BODY_TEXT")); + assert!( + output.contains("BODY_TEXT"), + "expected unmodified REST body response; sandbox output: {}", + output.replace(TEST_SECRET, "[redacted]") + ); let observations = server.wait_for_observations(1).await; assert_eq!(observations.len(), 1, "observations: {observations:?}"); assert!(observations[0].saw_placeholder); @@ -915,14 +925,24 @@ async fn credentialed_endpoint_gates_work_end_to_end() { CredentialSource::PolicyBinding, ) .await?; - assert!(literal.contains("BODY_TEXT")); + assert!( + literal.contains("BODY_TEXT"), + "expected literal body response; output: {}; observations: {:?}", + literal.replace(TEST_SECRET, "[redacted]"), + server.observations.lock().unwrap() + ); let rewritten = run_body_sandbox( server.port, EndpointMode::RestBody { rewrite: true }, CredentialSource::PolicyBinding, ) .await?; - assert!(rewritten.contains("BODY_REWRITTEN")); + assert!( + rewritten.contains("BODY_REWRITTEN"), + "expected rewritten body response; output: {}; observations: {:?}", + rewritten.replace(TEST_SECRET, "[redacted]"), + server.observations.lock().unwrap() + ); assert!(!rewritten.contains(TEST_SECRET)); assert!(!rewritten.contains(PLACEHOLDER_PREFIX)); let observations = server.wait_for_observations(3).await; diff --git a/e2e/rust/tests/warm_pool_preparation.rs b/e2e/rust/tests/warm_pool_preparation.rs new file mode 100644 index 0000000000..2bf4303900 --- /dev/null +++ b/e2e/rust/tests/warm_pool_preparation.rs @@ -0,0 +1,293 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +#![cfg(feature = "e2e-kubernetes-warm-pool")] + +//! Requires ci/values-warm-pool-preparation.yaml. Exercises preparation and single-use allocation. +use openshell_e2e::harness::{binary::openshell_cmd, sandbox::SandboxGuard}; +use serde_json::Value; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +async fn cli(args: &[&str]) -> String { + let output = tokio::time::timeout( + Duration::from_secs(300), + openshell_cmd().args(args).output(), + ) + .await + .expect("CLI timed out") + .expect("run CLI"); + assert!( + output.status.success(), + "CLI failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + String::from_utf8(output.stdout).unwrap() +} + +async fn inventory(resource: &str, selector: &str) -> Vec { + let context = + std::env::var("OPENSHELL_E2E_KUBE_CONTEXT_ACTIVE").expect("Kubernetes e2e context"); + let namespace = std::env::var("OPENSHELL_E2E_SANDBOX_NAMESPACE").expect("sandbox namespace"); + let output = tokio::process::Command::new("kubectl") + .args([ + "--context", + &context, + "-n", + &namespace, + "get", + resource, + "-l", + selector, + "-o", + "json", + ]) + .output() + .await + .expect("run kubectl"); + assert!( + output.status.success(), + "kubectl failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + serde_json::from_slice::(&output.stdout).unwrap()["items"] + .as_array() + .unwrap() + .clone() +} + +#[tokio::test] +async fn prepares_unassigned_pairs_and_retires_them_when_template_is_deleted() { + let suffix = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(); + let name = format!("pool-{suffix}"); + cli(&[ + "sandbox", + "template", + "create", + &name, + "--ready-within", + "1s", + "--max-burst", + "2", + "--env", + "WARM_POOL_ENV=prepared", + ]) + .await; + let template: Value = serde_json::from_str( + &cli(&["sandbox", "template", "get", &name, "--output", "json"]).await, + ) + .unwrap(); + let id = template["id"].as_str().expect("template ID"); + let selector = format!("openshell.ai/warm-pool={id}"); + let result = tokio::time::timeout(Duration::from_secs(300), async { + loop { + let pairs = inventory("sandboxes.agents.x-k8s.io", &selector).await; + if pairs.len() == 2 + && pairs.iter().all(|pair| { + pair["metadata"]["labels"]["openshell.ai/warm-pool-state"] == "ready" + }) + { + for pair in &pairs { + assert!(pair["metadata"]["annotations"]["openshell.ai/sandbox-id"].is_null()); + assert!(pair["metadata"]["labels"]["openshell.ai/sandbox-id"].is_null()); + let physical = pair["metadata"]["annotations"]["openshell.ai/warm-pair-id"] + .as_str() + .unwrap(); + let pods = + inventory("pods", &format!("openshell.ai/boundary-pair={physical}")).await; + assert_eq!(pods.len(), 2, "each pair owns exactly two Pods"); + for pod in &pods { + assert_eq!( + pod["metadata"]["labels"]["openshell.ai/template-name"], + name + ); + assert_eq!(pod["metadata"]["labels"]["openshell.ai/template-id"], id); + } + let proxy = pods + .iter() + .find(|pod| { + pod["metadata"]["labels"]["openshell.ai/boundary-role"] == "supervisor" + }) + .expect("proxy Pod"); + assert!( + proxy["status"]["conditions"] + .as_array() + .unwrap() + .iter() + .all(|c| c["type"] != "Ready" || c["status"] != "True"), + "idle proxy must not report activated readiness" + ); + } + return; + } + tokio::time::sleep(Duration::from_secs(2)).await; + } + }) + .await; + if result.is_err() { + let pairs = inventory("sandboxes.agents.x-k8s.io", &selector).await; + let states: Vec<_> = pairs.iter().map(|pair| serde_json::json!({ + "name": pair["metadata"]["name"], + "state": pair["metadata"]["labels"]["openshell.ai/warm-pool-state"], + "last_registration": pair["metadata"]["annotations"]["openshell.ai/warm-proxy-registered-at-ms"], + })).collect(); + eprintln!("Timed out with {} pool pairs: {states:?}", pairs.len()); + } + if result.is_err() { + cli(&["sandbox", "template", "delete", &name]).await; + panic!("two idle pairs should become ready without a logical sandbox"); + } + exercise_claimed_pair(&name, suffix, &selector).await; +} + +#[allow(clippy::too_many_lines)] // One lifecycle scenario keeps the shared two-pair capacity sequential. +async fn exercise_claimed_pair(name: &str, suffix: u128, selector: &str) { + let prepared = inventory("sandboxes.agents.x-k8s.io", selector).await; + let before_pods = inventory("pods", "openshell.ai/boundary-pair").await; + let logical_name = format!("claim-{suffix}"); + let mut sandbox = SandboxGuard::manage_existing(logical_name.clone()); + cli(&[ + "sandbox", + "create", + "--detach", + "--name", + &logical_name, + "--template", + name, + "--", + "sh", + "-c", + "echo \"$WARM_POOL_ENV\" > /sandbox/.warm-main; exec sleep infinity", + ]) + .await; + assert!( + sandbox + .exec(&["cat", "/sandbox/.warm-main"]) + .await + .unwrap() + .contains("prepared") + ); + let assigned_selector = format!("openshell.ai/sandbox-name={logical_name}"); + let assigned = inventory("sandboxes.agents.x-k8s.io", &assigned_selector).await; + assert_eq!(assigned.len(), 1); + let assigned = &assigned[0]; + assert!( + prepared + .iter() + .any(|p| p["metadata"]["uid"] == assigned["metadata"]["uid"]), + "create must claim an existing parent" + ); + assert!(assigned["metadata"]["labels"]["openshell.ai/warm-pool"].is_null()); + let physical = assigned["metadata"]["annotations"]["openshell.ai/warm-pair-id"] + .as_str() + .unwrap(); + let pod_selector = format!("openshell.ai/boundary-pair={physical}"); + let after_pods = inventory("pods", &pod_selector).await; + assert_eq!(after_pods.len(), 2); + tokio::time::timeout(Duration::from_secs(45), async { + loop { + let labeled = inventory("pods", &assigned_selector).await; + if labeled.len() == 2 + && labeled.iter().all(|pod| { + pod["metadata"]["labels"]["openshell.ai/sandbox-id"] + == assigned["metadata"]["labels"]["openshell.ai/sandbox-id"] + && after_pods + .iter() + .any(|prepared| prepared["metadata"]["uid"] == pod["metadata"]["uid"]) + }) + { + break; + } + tokio::time::sleep(Duration::from_millis(100)).await; + } + }) + .await + .expect("both prepared Pods should acquire the logical sandbox labels"); + for pod in &after_pods { + assert_eq!( + pod["metadata"]["labels"]["openshell.ai/template-name"], + name + ); + assert_eq!( + pod["metadata"]["labels"]["openshell.ai/template-id"], + assigned["metadata"]["labels"]["openshell.ai/template-id"] + ); + assert!( + before_pods + .iter() + .any(|before| before["metadata"]["uid"] == pod["metadata"]["uid"]), + "assignment must preserve both prepared Pods" + ); + } + sandbox + .exec(&["sh", "-c", "echo warm-workspace > /sandbox/.warm-test"]) + .await + .unwrap(); + tokio::time::timeout(Duration::from_secs(180), async { + loop { + let spares = inventory("sandboxes.agents.x-k8s.io", selector).await; + if spares.len() == 2 + && spares + .iter() + .all(|p| p["metadata"]["labels"]["openshell.ai/warm-pool-state"] == "ready") + { + break; + } + tokio::time::sleep(Duration::from_secs(2)).await; + } + }) + .await + .expect("claim should replenish the spare pool"); + cli(&["sandbox", "template", "delete", name]).await; + tokio::time::timeout(Duration::from_secs(120), async { + while !inventory("sandboxes.agents.x-k8s.io", selector) + .await + .is_empty() + { + tokio::time::sleep(Duration::from_secs(2)).await; + } + }) + .await + .expect("template deletion should retire spare inventory"); + assert_eq!( + inventory("sandboxes.agents.x-k8s.io", &assigned_selector) + .await + .len(), + 1, + "template deletion must preserve assigned pairs" + ); + cli(&["sandbox", "stop", &logical_name]).await; + cli(&["sandbox", "start", &logical_name]).await; + assert!( + sandbox + .exec(&["cat", "/sandbox/.warm-test"]) + .await + .unwrap() + .contains("warm-workspace") + ); + sandbox.cleanup().await; + tokio::time::timeout(Duration::from_secs(120), async { + while !inventory("sandboxes.agents.x-k8s.io", &assigned_selector) + .await + .is_empty() + || !inventory("pods", &pod_selector).await.is_empty() + { + tokio::time::sleep(Duration::from_secs(2)).await; + } + }) + .await + .expect("deleting the logical sandbox must delete the pair"); + let mut cold = SandboxGuard::create(&[]) + .await + .expect("cold fallback remains available"); + let cold_parent = inventory( + "sandboxes.agents.x-k8s.io", + &format!("openshell.ai/sandbox-name={}", cold.name), + ) + .await; + assert_eq!(cold_parent.len(), 1); + assert!(cold_parent[0]["metadata"]["annotations"]["openshell.ai/warm-pair-id"].is_null()); + cold.cleanup().await; +} diff --git a/proto/compute_driver.proto b/proto/compute_driver.proto index 36fa4eeb76..5147fd14b7 100644 --- a/proto/compute_driver.proto +++ b/proto/compute_driver.proto @@ -23,6 +23,13 @@ import "sandbox.proto"; // fields so independently versioned external drivers remain // forward-compatible. service ComputeDriver { + // Discover an eligible pair without claiming it; persist the candidate before CreateSandbox. + // Unsupported drivers return Unimplemented. + rpc SelectWarmPair(SelectWarmPairRequest) returns (SelectWarmPairResponse); + // Replace the desired inventory snapshot. Unsupported drivers return Unimplemented. + // Contains public preparation trust only; never operational credentials. + rpc SyncWarmPools(SyncWarmPoolsRequest) returns (SyncWarmPoolsResponse); + // Report driver capabilities and defaults. rpc GetCapabilities(GetCapabilitiesRequest) returns (GetCapabilitiesResponse); @@ -105,11 +112,27 @@ message GetCapabilitiesResponse { message AuthenticateSandboxRequest { // Opaque credential whose format and verification are owned by the driver. string credential = 1 [(openshell.options.v1.secret) = true]; + // Request a live prepared-pair identity for registration, not tenant authority. + bool supervisor_registration = 2; } message AuthenticateSandboxResponse { // Stable gateway-assigned sandbox ID authenticated by the driver. string sandbox_id = 1; + SupervisorRegistration registration = 2; +} + +// Driver-verified registration coordinates. Never accepted from the proxy itself. +message SupervisorRegistration { + string instance_id = 1; + string sandbox_resource_uid = 2; + string workload_pod_uid = 3; + // Protected physical runtime descriptor, without operational credentials. + bytes backend_descriptor = 4; + string main_process_spec = 5; + string runtime_generation = 6; + string session_id = 7; + map resource_binding = 8; } // Static portable resource request forms supported by a compute driver. @@ -379,6 +402,7 @@ message ListSandboxesResponse { } message CreateSandboxRequest { + WarmPairCandidate warm_pair = 2; // Sandbox configuration to provision on the compute platform. DriverSandbox sandbox = 1; } @@ -410,6 +434,10 @@ message StartSandboxRequest { message StartSandboxResponse {} message DeleteSandboxRequest { + // Allocation recovery must never retire a candidate already claimed by this sandbox. + bool only_unassigned_pair = 4; + // Fence a persisted candidate even if the claim has not completed. + WarmPairCandidate warm_pair = 3; // Stable sandbox ID stored by the gateway. string sandbox_id = 1; // Compute-runtime name used by the driver. @@ -464,3 +492,39 @@ message DeleteWorkspaceRequest { } message DeleteWorkspaceResponse {} + +// Complete gateway-owned template snapshot; an empty list retires spare inventory. +message SyncWarmPoolsRequest { + repeated WarmPoolTarget pools = 1; + string gateway_id = 2; + // JSON array of SessionVerificationKey public verification keys. + bytes verification_keys = 3; +} +message WarmPoolTarget { + string template_id = 1; + string workspace = 2; + uint32 target = 3; + DriverSandboxSpec spec = 4; + // Display metadata only; template_id remains the stable pool identity. + string template_name = 5; +} +message SyncWarmPoolsResponse {} + +// Physical handle, never an authorization credential. +message WarmPairCandidate { + string namespace = 1; + string name = 2; + string uid = 3; + string template_id = 4; + string fingerprint = 5; + string runtime_generation = 6; +} +message SelectWarmPairRequest { + string template_id = 1; + DriverSandbox sandbox = 2; + // Physical UIDs already rejected or reserved by another allocation in this retry. + repeated string excluded_candidate_uids = 3; +} +message SelectWarmPairResponse { + WarmPairCandidate candidate = 1; +} diff --git a/proto/openshell.proto b/proto/openshell.proto index 856254a943..ec0d54fd40 100644 --- a/proto/openshell.proto +++ b/proto/openshell.proto @@ -689,6 +689,13 @@ service OpenShell { }; } + // Wait for the driver-verified proxy's durable assignment before activation. + rpc RegisterSupervisor(RegisterSupervisorRequest) returns (RegisterSupervisorResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "sandbox" + }; + } + // Exchange a sandbox-bootstrap credential (e.g. a Kubernetes projected // ServiceAccount token) for a gateway-minted JWT bound to the calling // sandbox's UUID. Used by the Kubernetes driver path; singleplayer @@ -781,6 +788,17 @@ service OpenShell { } } +// Identity is established by the projected credential in the request headers. +message RegisterSupervisorRequest {} + +message RegisterSupervisorResponse { + string sandbox_id = 1; + string sandbox_name = 2; + bytes backend_descriptor = 3; + bytes auth_bundle = 4 [(openshell.options.v1.secret) = true]; + string main_process_spec = 5; +} + // 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). diff --git a/sdk/go/proto/openshellv1/openshell.pb.go b/sdk/go/proto/openshellv1/openshell.pb.go index 5ef25ec798..7e02986613 100644 --- a/sdk/go/proto/openshellv1/openshell.pb.go +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -1142,6 +1142,119 @@ func (EndpointResult) EnumDescriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{17} } +// Identity is established by the projected credential in the request headers. +type RegisterSupervisorRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterSupervisorRequest) Reset() { + *x = RegisterSupervisorRequest{} + mi := &file_openshell_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterSupervisorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterSupervisorRequest) ProtoMessage() {} + +func (x *RegisterSupervisorRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[0] + 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 RegisterSupervisorRequest.ProtoReflect.Descriptor instead. +func (*RegisterSupervisorRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{0} +} + +type RegisterSupervisorResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + SandboxName string `protobuf:"bytes,2,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` + BackendDescriptor []byte `protobuf:"bytes,3,opt,name=backend_descriptor,json=backendDescriptor,proto3" json:"backend_descriptor,omitempty"` + AuthBundle []byte `protobuf:"bytes,4,opt,name=auth_bundle,json=authBundle,proto3" json:"auth_bundle,omitempty"` + MainProcessSpec string `protobuf:"bytes,5,opt,name=main_process_spec,json=mainProcessSpec,proto3" json:"main_process_spec,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterSupervisorResponse) Reset() { + *x = RegisterSupervisorResponse{} + mi := &file_openshell_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterSupervisorResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterSupervisorResponse) ProtoMessage() {} + +func (x *RegisterSupervisorResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[1] + 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 RegisterSupervisorResponse.ProtoReflect.Descriptor instead. +func (*RegisterSupervisorResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{1} +} + +func (x *RegisterSupervisorResponse) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *RegisterSupervisorResponse) GetSandboxName() string { + if x != nil { + return x.SandboxName + } + return "" +} + +func (x *RegisterSupervisorResponse) GetBackendDescriptor() []byte { + if x != nil { + return x.BackendDescriptor + } + return nil +} + +func (x *RegisterSupervisorResponse) GetAuthBundle() []byte { + if x != nil { + return x.AuthBundle + } + return nil +} + +func (x *RegisterSupervisorResponse) GetMainProcessSpec() string { + if x != nil { + return x.MainProcessSpec + } + return "" +} + // 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). @@ -1153,7 +1266,7 @@ type IssueSandboxTokenRequest struct { func (x *IssueSandboxTokenRequest) Reset() { *x = IssueSandboxTokenRequest{} - mi := &file_openshell_proto_msgTypes[0] + mi := &file_openshell_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1165,7 +1278,7 @@ func (x *IssueSandboxTokenRequest) String() string { func (*IssueSandboxTokenRequest) ProtoMessage() {} func (x *IssueSandboxTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[0] + mi := &file_openshell_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1178,7 +1291,7 @@ func (x *IssueSandboxTokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use IssueSandboxTokenRequest.ProtoReflect.Descriptor instead. func (*IssueSandboxTokenRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{0} + return file_openshell_proto_rawDescGZIP(), []int{2} } // IssueSandboxToken response. The supervisor caches the returned token in @@ -1196,7 +1309,7 @@ type IssueSandboxTokenResponse struct { func (x *IssueSandboxTokenResponse) Reset() { *x = IssueSandboxTokenResponse{} - mi := &file_openshell_proto_msgTypes[1] + mi := &file_openshell_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1208,7 +1321,7 @@ func (x *IssueSandboxTokenResponse) String() string { func (*IssueSandboxTokenResponse) ProtoMessage() {} func (x *IssueSandboxTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[1] + mi := &file_openshell_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1221,7 +1334,7 @@ func (x *IssueSandboxTokenResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IssueSandboxTokenResponse.ProtoReflect.Descriptor instead. func (*IssueSandboxTokenResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{1} + return file_openshell_proto_rawDescGZIP(), []int{3} } func (x *IssueSandboxTokenResponse) GetToken() string { @@ -1254,7 +1367,7 @@ type RefreshSandboxTokenRequest struct { func (x *RefreshSandboxTokenRequest) Reset() { *x = RefreshSandboxTokenRequest{} - mi := &file_openshell_proto_msgTypes[2] + mi := &file_openshell_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1266,7 +1379,7 @@ func (x *RefreshSandboxTokenRequest) String() string { func (*RefreshSandboxTokenRequest) ProtoMessage() {} func (x *RefreshSandboxTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[2] + mi := &file_openshell_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1279,7 +1392,7 @@ func (x *RefreshSandboxTokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RefreshSandboxTokenRequest.ProtoReflect.Descriptor instead. func (*RefreshSandboxTokenRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{2} + return file_openshell_proto_rawDescGZIP(), []int{4} } func (x *RefreshSandboxTokenRequest) GetExtensionServiceNames() []string { @@ -1314,7 +1427,7 @@ type RefreshSandboxTokenResponse struct { func (x *RefreshSandboxTokenResponse) Reset() { *x = RefreshSandboxTokenResponse{} - mi := &file_openshell_proto_msgTypes[3] + mi := &file_openshell_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1326,7 +1439,7 @@ func (x *RefreshSandboxTokenResponse) String() string { func (*RefreshSandboxTokenResponse) ProtoMessage() {} func (x *RefreshSandboxTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[3] + mi := &file_openshell_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1339,7 +1452,7 @@ func (x *RefreshSandboxTokenResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RefreshSandboxTokenResponse.ProtoReflect.Descriptor instead. func (*RefreshSandboxTokenResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{3} + return file_openshell_proto_rawDescGZIP(), []int{5} } func (x *RefreshSandboxTokenResponse) GetToken() string { @@ -1400,7 +1513,7 @@ type HealthRequest struct { func (x *HealthRequest) Reset() { *x = HealthRequest{} - mi := &file_openshell_proto_msgTypes[4] + mi := &file_openshell_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1412,7 +1525,7 @@ func (x *HealthRequest) String() string { func (*HealthRequest) ProtoMessage() {} func (x *HealthRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[4] + mi := &file_openshell_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1425,7 +1538,7 @@ func (x *HealthRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HealthRequest.ProtoReflect.Descriptor instead. func (*HealthRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{4} + return file_openshell_proto_rawDescGZIP(), []int{6} } // Health check response. @@ -1441,7 +1554,7 @@ type HealthResponse struct { func (x *HealthResponse) Reset() { *x = HealthResponse{} - mi := &file_openshell_proto_msgTypes[5] + mi := &file_openshell_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1453,7 +1566,7 @@ func (x *HealthResponse) String() string { func (*HealthResponse) ProtoMessage() {} func (x *HealthResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[5] + mi := &file_openshell_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1466,7 +1579,7 @@ func (x *HealthResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HealthResponse.ProtoReflect.Descriptor instead. func (*HealthResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{5} + return file_openshell_proto_rawDescGZIP(), []int{7} } func (x *HealthResponse) GetStatus() ServiceStatus { @@ -1492,7 +1605,7 @@ type GetCurrentUserRequest struct { func (x *GetCurrentUserRequest) Reset() { *x = GetCurrentUserRequest{} - mi := &file_openshell_proto_msgTypes[6] + mi := &file_openshell_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1504,7 +1617,7 @@ func (x *GetCurrentUserRequest) String() string { func (*GetCurrentUserRequest) ProtoMessage() {} func (x *GetCurrentUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[6] + mi := &file_openshell_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1517,7 +1630,7 @@ func (x *GetCurrentUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCurrentUserRequest.ProtoReflect.Descriptor instead. func (*GetCurrentUserRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{6} + return file_openshell_proto_rawDescGZIP(), []int{8} } // Authenticated user identity as validated by the gateway. @@ -1539,7 +1652,7 @@ type GetCurrentUserResponse struct { func (x *GetCurrentUserResponse) Reset() { *x = GetCurrentUserResponse{} - mi := &file_openshell_proto_msgTypes[7] + mi := &file_openshell_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1551,7 +1664,7 @@ func (x *GetCurrentUserResponse) String() string { func (*GetCurrentUserResponse) ProtoMessage() {} func (x *GetCurrentUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[7] + mi := &file_openshell_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1564,7 +1677,7 @@ func (x *GetCurrentUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCurrentUserResponse.ProtoReflect.Descriptor instead. func (*GetCurrentUserResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{7} + return file_openshell_proto_rawDescGZIP(), []int{9} } func (x *GetCurrentUserResponse) GetSubject() string { @@ -1611,7 +1724,7 @@ type GetGatewayInfoRequest struct { func (x *GetGatewayInfoRequest) Reset() { *x = GetGatewayInfoRequest{} - mi := &file_openshell_proto_msgTypes[8] + mi := &file_openshell_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1623,7 +1736,7 @@ func (x *GetGatewayInfoRequest) String() string { func (*GetGatewayInfoRequest) ProtoMessage() {} func (x *GetGatewayInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[8] + mi := &file_openshell_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1636,7 +1749,7 @@ func (x *GetGatewayInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetGatewayInfoRequest.ProtoReflect.Descriptor instead. func (*GetGatewayInfoRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{8} + return file_openshell_proto_rawDescGZIP(), []int{10} } // Gateway info response. @@ -1657,7 +1770,7 @@ type GetGatewayInfoResponse struct { func (x *GetGatewayInfoResponse) Reset() { *x = GetGatewayInfoResponse{} - mi := &file_openshell_proto_msgTypes[9] + mi := &file_openshell_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1669,7 +1782,7 @@ func (x *GetGatewayInfoResponse) String() string { func (*GetGatewayInfoResponse) ProtoMessage() {} func (x *GetGatewayInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[9] + mi := &file_openshell_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1682,7 +1795,7 @@ func (x *GetGatewayInfoResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetGatewayInfoResponse.ProtoReflect.Descriptor instead. func (*GetGatewayInfoResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{9} + return file_openshell_proto_rawDescGZIP(), []int{11} } func (x *GetGatewayInfoResponse) GetStatus() ServiceStatus { @@ -1735,7 +1848,7 @@ type NegotiatedExtensionInfo struct { func (x *NegotiatedExtensionInfo) Reset() { *x = NegotiatedExtensionInfo{} - mi := &file_openshell_proto_msgTypes[10] + mi := &file_openshell_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1747,7 +1860,7 @@ func (x *NegotiatedExtensionInfo) String() string { func (*NegotiatedExtensionInfo) ProtoMessage() {} func (x *NegotiatedExtensionInfo) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[10] + mi := &file_openshell_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1760,7 +1873,7 @@ func (x *NegotiatedExtensionInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use NegotiatedExtensionInfo.ProtoReflect.Descriptor instead. func (*NegotiatedExtensionInfo) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{10} + return file_openshell_proto_rawDescGZIP(), []int{12} } func (x *NegotiatedExtensionInfo) GetKind() ExtensionKind { @@ -1832,7 +1945,7 @@ type ComputeDriverInfo struct { func (x *ComputeDriverInfo) Reset() { *x = ComputeDriverInfo{} - mi := &file_openshell_proto_msgTypes[11] + mi := &file_openshell_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1844,7 +1957,7 @@ func (x *ComputeDriverInfo) String() string { func (*ComputeDriverInfo) ProtoMessage() {} func (x *ComputeDriverInfo) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[11] + mi := &file_openshell_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1857,7 +1970,7 @@ func (x *ComputeDriverInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ComputeDriverInfo.ProtoReflect.Descriptor instead. func (*ComputeDriverInfo) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{11} + return file_openshell_proto_rawDescGZIP(), []int{13} } func (x *ComputeDriverInfo) GetName() string { @@ -1889,7 +2002,7 @@ type ComputeDriverCapabilities struct { func (x *ComputeDriverCapabilities) Reset() { *x = ComputeDriverCapabilities{} - mi := &file_openshell_proto_msgTypes[12] + mi := &file_openshell_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1901,7 +2014,7 @@ func (x *ComputeDriverCapabilities) String() string { func (*ComputeDriverCapabilities) ProtoMessage() {} func (x *ComputeDriverCapabilities) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[12] + mi := &file_openshell_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1914,7 +2027,7 @@ func (x *ComputeDriverCapabilities) ProtoReflect() protoreflect.Message { // Deprecated: Use ComputeDriverCapabilities.ProtoReflect.Descriptor instead. func (*ComputeDriverCapabilities) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{12} + return file_openshell_proto_rawDescGZIP(), []int{14} } func (x *ComputeDriverCapabilities) GetDriverName() string { @@ -1951,7 +2064,7 @@ type ResourceCapabilities struct { func (x *ResourceCapabilities) Reset() { *x = ResourceCapabilities{} - mi := &file_openshell_proto_msgTypes[13] + mi := &file_openshell_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1963,7 +2076,7 @@ func (x *ResourceCapabilities) String() string { func (*ResourceCapabilities) ProtoMessage() {} func (x *ResourceCapabilities) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[13] + mi := &file_openshell_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1976,7 +2089,7 @@ func (x *ResourceCapabilities) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceCapabilities.ProtoReflect.Descriptor instead. func (*ResourceCapabilities) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{13} + return file_openshell_proto_rawDescGZIP(), []int{15} } func (x *ResourceCapabilities) GetCpu() *CpuResourceCapabilities { @@ -2010,7 +2123,7 @@ type CpuResourceCapabilities struct { func (x *CpuResourceCapabilities) Reset() { *x = CpuResourceCapabilities{} - mi := &file_openshell_proto_msgTypes[14] + mi := &file_openshell_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2022,7 +2135,7 @@ func (x *CpuResourceCapabilities) String() string { func (*CpuResourceCapabilities) ProtoMessage() {} func (x *CpuResourceCapabilities) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[14] + mi := &file_openshell_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2035,7 +2148,7 @@ func (x *CpuResourceCapabilities) ProtoReflect() protoreflect.Message { // Deprecated: Use CpuResourceCapabilities.ProtoReflect.Descriptor instead. func (*CpuResourceCapabilities) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{14} + return file_openshell_proto_rawDescGZIP(), []int{16} } func (x *CpuResourceCapabilities) GetLimitSupported() bool { @@ -2055,7 +2168,7 @@ type MemoryResourceCapabilities struct { func (x *MemoryResourceCapabilities) Reset() { *x = MemoryResourceCapabilities{} - mi := &file_openshell_proto_msgTypes[15] + mi := &file_openshell_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2067,7 +2180,7 @@ func (x *MemoryResourceCapabilities) String() string { func (*MemoryResourceCapabilities) ProtoMessage() {} func (x *MemoryResourceCapabilities) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[15] + mi := &file_openshell_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2080,7 +2193,7 @@ func (x *MemoryResourceCapabilities) ProtoReflect() protoreflect.Message { // Deprecated: Use MemoryResourceCapabilities.ProtoReflect.Descriptor instead. func (*MemoryResourceCapabilities) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{15} + return file_openshell_proto_rawDescGZIP(), []int{17} } func (x *MemoryResourceCapabilities) GetLimitSupported() bool { @@ -2102,7 +2215,7 @@ type GpuResourceCapabilities struct { func (x *GpuResourceCapabilities) Reset() { *x = GpuResourceCapabilities{} - mi := &file_openshell_proto_msgTypes[16] + mi := &file_openshell_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2114,7 +2227,7 @@ func (x *GpuResourceCapabilities) String() string { func (*GpuResourceCapabilities) ProtoMessage() {} func (x *GpuResourceCapabilities) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[16] + mi := &file_openshell_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2127,7 +2240,7 @@ func (x *GpuResourceCapabilities) ProtoReflect() protoreflect.Message { // Deprecated: Use GpuResourceCapabilities.ProtoReflect.Descriptor instead. func (*GpuResourceCapabilities) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{16} + return file_openshell_proto_rawDescGZIP(), []int{18} } func (x *GpuResourceCapabilities) GetDefaultSelectionSupported() bool { @@ -2168,7 +2281,7 @@ type Sandbox struct { func (x *Sandbox) Reset() { *x = Sandbox{} - mi := &file_openshell_proto_msgTypes[17] + mi := &file_openshell_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2180,7 +2293,7 @@ func (x *Sandbox) String() string { func (*Sandbox) ProtoMessage() {} func (x *Sandbox) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[17] + mi := &file_openshell_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2193,7 +2306,7 @@ func (x *Sandbox) ProtoReflect() protoreflect.Message { // Deprecated: Use Sandbox.ProtoReflect.Descriptor instead. func (*Sandbox) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{17} + return file_openshell_proto_rawDescGZIP(), []int{19} } func (x *Sandbox) GetMetadata() *datamodelv1.ObjectMeta { @@ -2255,7 +2368,7 @@ type SandboxSpec struct { func (x *SandboxSpec) Reset() { *x = SandboxSpec{} - mi := &file_openshell_proto_msgTypes[18] + mi := &file_openshell_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2267,7 +2380,7 @@ func (x *SandboxSpec) String() string { func (*SandboxSpec) ProtoMessage() {} func (x *SandboxSpec) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[18] + mi := &file_openshell_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2280,7 +2393,7 @@ func (x *SandboxSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxSpec.ProtoReflect.Descriptor instead. func (*SandboxSpec) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{18} + return file_openshell_proto_rawDescGZIP(), []int{20} } func (x *SandboxSpec) GetLogLevel() string { @@ -2356,7 +2469,7 @@ type ResourceRequirements struct { func (x *ResourceRequirements) Reset() { *x = ResourceRequirements{} - mi := &file_openshell_proto_msgTypes[19] + mi := &file_openshell_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2368,7 +2481,7 @@ func (x *ResourceRequirements) String() string { func (*ResourceRequirements) ProtoMessage() {} func (x *ResourceRequirements) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[19] + mi := &file_openshell_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2381,7 +2494,7 @@ func (x *ResourceRequirements) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceRequirements.ProtoReflect.Descriptor instead. func (*ResourceRequirements) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{19} + return file_openshell_proto_rawDescGZIP(), []int{21} } func (x *ResourceRequirements) GetGpu() *GpuResourceRequirements { @@ -2403,7 +2516,7 @@ type GpuResourceRequirements struct { func (x *GpuResourceRequirements) Reset() { *x = GpuResourceRequirements{} - mi := &file_openshell_proto_msgTypes[20] + mi := &file_openshell_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2415,7 +2528,7 @@ func (x *GpuResourceRequirements) String() string { func (*GpuResourceRequirements) ProtoMessage() {} func (x *GpuResourceRequirements) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[20] + mi := &file_openshell_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2428,7 +2541,7 @@ func (x *GpuResourceRequirements) ProtoReflect() protoreflect.Message { // Deprecated: Use GpuResourceRequirements.ProtoReflect.Descriptor instead. func (*GpuResourceRequirements) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{20} + return file_openshell_proto_rawDescGZIP(), []int{22} } func (x *GpuResourceRequirements) GetCount() uint32 { @@ -2477,7 +2590,7 @@ type SandboxTemplate struct { func (x *SandboxTemplate) Reset() { *x = SandboxTemplate{} - mi := &file_openshell_proto_msgTypes[21] + mi := &file_openshell_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2489,7 +2602,7 @@ func (x *SandboxTemplate) String() string { func (*SandboxTemplate) ProtoMessage() {} func (x *SandboxTemplate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[21] + mi := &file_openshell_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2502,7 +2615,7 @@ func (x *SandboxTemplate) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxTemplate.ProtoReflect.Descriptor instead. func (*SandboxTemplate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{21} + return file_openshell_proto_rawDescGZIP(), []int{23} } func (x *SandboxTemplate) GetImage() string { @@ -2586,7 +2699,7 @@ type SandboxWorkloadTemplate struct { func (x *SandboxWorkloadTemplate) Reset() { *x = SandboxWorkloadTemplate{} - mi := &file_openshell_proto_msgTypes[22] + mi := &file_openshell_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2598,7 +2711,7 @@ func (x *SandboxWorkloadTemplate) String() string { func (*SandboxWorkloadTemplate) ProtoMessage() {} func (x *SandboxWorkloadTemplate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[22] + mi := &file_openshell_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2611,7 +2724,7 @@ func (x *SandboxWorkloadTemplate) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxWorkloadTemplate.ProtoReflect.Descriptor instead. func (*SandboxWorkloadTemplate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{22} + return file_openshell_proto_rawDescGZIP(), []int{24} } func (x *SandboxWorkloadTemplate) GetMetadata() *datamodelv1.ObjectMeta { @@ -2642,7 +2755,7 @@ type SandboxWorkloadTemplateSpec struct { func (x *SandboxWorkloadTemplateSpec) Reset() { *x = SandboxWorkloadTemplateSpec{} - mi := &file_openshell_proto_msgTypes[23] + mi := &file_openshell_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2654,7 +2767,7 @@ func (x *SandboxWorkloadTemplateSpec) String() string { func (*SandboxWorkloadTemplateSpec) ProtoMessage() {} func (x *SandboxWorkloadTemplateSpec) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[23] + mi := &file_openshell_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2667,7 +2780,7 @@ func (x *SandboxWorkloadTemplateSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxWorkloadTemplateSpec.ProtoReflect.Descriptor instead. func (*SandboxWorkloadTemplateSpec) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{23} + return file_openshell_proto_rawDescGZIP(), []int{25} } func (x *SandboxWorkloadTemplateSpec) GetWorkload() *SandboxWorkloadConfig { @@ -2705,7 +2818,7 @@ type SandboxWorkloadConfig struct { func (x *SandboxWorkloadConfig) Reset() { *x = SandboxWorkloadConfig{} - mi := &file_openshell_proto_msgTypes[24] + mi := &file_openshell_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2717,7 +2830,7 @@ func (x *SandboxWorkloadConfig) String() string { func (*SandboxWorkloadConfig) ProtoMessage() {} func (x *SandboxWorkloadConfig) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[24] + mi := &file_openshell_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2730,7 +2843,7 @@ func (x *SandboxWorkloadConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxWorkloadConfig.ProtoReflect.Descriptor instead. func (*SandboxWorkloadConfig) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{24} + return file_openshell_proto_rawDescGZIP(), []int{26} } func (x *SandboxWorkloadConfig) GetImage() string { @@ -2770,7 +2883,7 @@ type SandboxResources struct { func (x *SandboxResources) Reset() { *x = SandboxResources{} - mi := &file_openshell_proto_msgTypes[25] + mi := &file_openshell_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2782,7 +2895,7 @@ func (x *SandboxResources) String() string { func (*SandboxResources) ProtoMessage() {} func (x *SandboxResources) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[25] + mi := &file_openshell_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2795,7 +2908,7 @@ func (x *SandboxResources) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxResources.ProtoReflect.Descriptor instead. func (*SandboxResources) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{25} + return file_openshell_proto_rawDescGZIP(), []int{27} } func (x *SandboxResources) GetCpu() string { @@ -2828,7 +2941,7 @@ type SandboxServiceLevel struct { func (x *SandboxServiceLevel) Reset() { *x = SandboxServiceLevel{} - mi := &file_openshell_proto_msgTypes[26] + mi := &file_openshell_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2840,7 +2953,7 @@ func (x *SandboxServiceLevel) String() string { func (*SandboxServiceLevel) ProtoMessage() {} func (x *SandboxServiceLevel) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[26] + mi := &file_openshell_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2853,7 +2966,7 @@ func (x *SandboxServiceLevel) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxServiceLevel.ProtoReflect.Descriptor instead. func (*SandboxServiceLevel) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{26} + return file_openshell_proto_rawDescGZIP(), []int{28} } func (x *SandboxServiceLevel) GetStartup() *SandboxStartup { @@ -2873,7 +2986,7 @@ type SandboxStartup struct { func (x *SandboxStartup) Reset() { *x = SandboxStartup{} - mi := &file_openshell_proto_msgTypes[27] + mi := &file_openshell_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2885,7 +2998,7 @@ func (x *SandboxStartup) String() string { func (*SandboxStartup) ProtoMessage() {} func (x *SandboxStartup) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[27] + mi := &file_openshell_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2898,7 +3011,7 @@ func (x *SandboxStartup) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStartup.ProtoReflect.Descriptor instead. func (*SandboxStartup) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{27} + return file_openshell_proto_rawDescGZIP(), []int{29} } func (x *SandboxStartup) GetReadyWithin() *durationpb.Duration { @@ -2925,7 +3038,7 @@ type SandboxWorkloadTemplateProvenance struct { func (x *SandboxWorkloadTemplateProvenance) Reset() { *x = SandboxWorkloadTemplateProvenance{} - mi := &file_openshell_proto_msgTypes[28] + mi := &file_openshell_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2937,7 +3050,7 @@ func (x *SandboxWorkloadTemplateProvenance) String() string { func (*SandboxWorkloadTemplateProvenance) ProtoMessage() {} func (x *SandboxWorkloadTemplateProvenance) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[28] + mi := &file_openshell_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2950,7 +3063,7 @@ func (x *SandboxWorkloadTemplateProvenance) ProtoReflect() protoreflect.Message // Deprecated: Use SandboxWorkloadTemplateProvenance.ProtoReflect.Descriptor instead. func (*SandboxWorkloadTemplateProvenance) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{28} + return file_openshell_proto_rawDescGZIP(), []int{30} } func (x *SandboxWorkloadTemplateProvenance) GetName() string { @@ -3007,7 +3120,7 @@ type SandboxStatus struct { func (x *SandboxStatus) Reset() { *x = SandboxStatus{} - mi := &file_openshell_proto_msgTypes[29] + mi := &file_openshell_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3019,7 +3132,7 @@ func (x *SandboxStatus) String() string { func (*SandboxStatus) ProtoMessage() {} func (x *SandboxStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[29] + mi := &file_openshell_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3032,7 +3145,7 @@ func (x *SandboxStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStatus.ProtoReflect.Descriptor instead. func (*SandboxStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{29} + return file_openshell_proto_rawDescGZIP(), []int{31} } func (x *SandboxStatus) GetAgentPod() string { @@ -3138,7 +3251,7 @@ type SandboxCondition struct { func (x *SandboxCondition) Reset() { *x = SandboxCondition{} - mi := &file_openshell_proto_msgTypes[30] + mi := &file_openshell_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3150,7 +3263,7 @@ func (x *SandboxCondition) String() string { func (*SandboxCondition) ProtoMessage() {} func (x *SandboxCondition) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[30] + mi := &file_openshell_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3163,7 +3276,7 @@ func (x *SandboxCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxCondition.ProtoReflect.Descriptor instead. func (*SandboxCondition) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{30} + return file_openshell_proto_rawDescGZIP(), []int{32} } func (x *SandboxCondition) GetType() string { @@ -3222,7 +3335,7 @@ type PlatformEvent struct { func (x *PlatformEvent) Reset() { *x = PlatformEvent{} - mi := &file_openshell_proto_msgTypes[31] + mi := &file_openshell_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3234,7 +3347,7 @@ func (x *PlatformEvent) String() string { func (*PlatformEvent) ProtoMessage() {} func (x *PlatformEvent) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[31] + mi := &file_openshell_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3247,7 +3360,7 @@ func (x *PlatformEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use PlatformEvent.ProtoReflect.Descriptor instead. func (*PlatformEvent) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{31} + return file_openshell_proto_rawDescGZIP(), []int{33} } func (x *PlatformEvent) GetEventTime() *timestamppb.Timestamp { @@ -3323,7 +3436,7 @@ type CreateSandboxRequest struct { func (x *CreateSandboxRequest) Reset() { *x = CreateSandboxRequest{} - mi := &file_openshell_proto_msgTypes[32] + mi := &file_openshell_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3335,7 +3448,7 @@ func (x *CreateSandboxRequest) String() string { func (*CreateSandboxRequest) ProtoMessage() {} func (x *CreateSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[32] + mi := &file_openshell_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3348,7 +3461,7 @@ func (x *CreateSandboxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateSandboxRequest.ProtoReflect.Descriptor instead. func (*CreateSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{32} + return file_openshell_proto_rawDescGZIP(), []int{34} } func (x *CreateSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -3428,7 +3541,7 @@ type CreateSandboxTemplateRequest struct { func (x *CreateSandboxTemplateRequest) Reset() { *x = CreateSandboxTemplateRequest{} - mi := &file_openshell_proto_msgTypes[33] + mi := &file_openshell_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3440,7 +3553,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[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3453,7 +3566,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{35} } func (x *CreateSandboxTemplateRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -3488,7 +3601,7 @@ type GetSandboxTemplateRequest struct { func (x *GetSandboxTemplateRequest) Reset() { *x = GetSandboxTemplateRequest{} - mi := &file_openshell_proto_msgTypes[34] + mi := &file_openshell_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3500,7 +3613,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[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3513,7 +3626,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{36} } func (x *GetSandboxTemplateRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -3548,7 +3661,7 @@ type ListSandboxTemplatesRequest struct { func (x *ListSandboxTemplatesRequest) Reset() { *x = ListSandboxTemplatesRequest{} - mi := &file_openshell_proto_msgTypes[35] + mi := &file_openshell_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3560,7 +3673,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[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3573,7 +3686,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{37} } func (x *ListSandboxTemplatesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -3620,7 +3733,7 @@ type DeleteSandboxTemplateRequest struct { func (x *DeleteSandboxTemplateRequest) Reset() { *x = DeleteSandboxTemplateRequest{} - mi := &file_openshell_proto_msgTypes[36] + mi := &file_openshell_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3632,7 +3745,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[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3645,7 +3758,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{38} } func (x *DeleteSandboxTemplateRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -3685,7 +3798,7 @@ type SandboxTemplateResponse struct { func (x *SandboxTemplateResponse) Reset() { *x = SandboxTemplateResponse{} - mi := &file_openshell_proto_msgTypes[37] + mi := &file_openshell_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3697,7 +3810,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[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3710,7 +3823,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{39} } func (x *SandboxTemplateResponse) GetTemplate() *SandboxWorkloadTemplate { @@ -3731,7 +3844,7 @@ type ListSandboxTemplatesResponse struct { func (x *ListSandboxTemplatesResponse) Reset() { *x = ListSandboxTemplatesResponse{} - mi := &file_openshell_proto_msgTypes[38] + mi := &file_openshell_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3743,7 +3856,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[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3756,7 +3869,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{40} } func (x *ListSandboxTemplatesResponse) GetTemplates() []*SandboxWorkloadTemplate { @@ -3782,7 +3895,7 @@ type DeleteSandboxTemplateResponse struct { func (x *DeleteSandboxTemplateResponse) Reset() { *x = DeleteSandboxTemplateResponse{} - mi := &file_openshell_proto_msgTypes[39] + mi := &file_openshell_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3794,7 +3907,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[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3807,7 +3920,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{41} } func (x *DeleteSandboxTemplateResponse) GetOutcome() DeletionOutcome { @@ -3834,7 +3947,7 @@ type BeginRootfsTarStagingRequest struct { func (x *BeginRootfsTarStagingRequest) Reset() { *x = BeginRootfsTarStagingRequest{} - mi := &file_openshell_proto_msgTypes[40] + mi := &file_openshell_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3846,7 +3959,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[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3859,7 +3972,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{42} } func (x *BeginRootfsTarStagingRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -3902,7 +4015,7 @@ type BeginRootfsTarStagingResponse struct { func (x *BeginRootfsTarStagingResponse) Reset() { *x = BeginRootfsTarStagingResponse{} - mi := &file_openshell_proto_msgTypes[41] + mi := &file_openshell_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3914,7 +4027,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[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3927,7 +4040,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{43} } func (x *BeginRootfsTarStagingResponse) GetStagingToken() string { @@ -3970,7 +4083,7 @@ type GetSandboxRequest struct { func (x *GetSandboxRequest) Reset() { *x = GetSandboxRequest{} - mi := &file_openshell_proto_msgTypes[42] + mi := &file_openshell_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3982,7 +4095,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[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3995,7 +4108,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{44} } func (x *GetSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4031,7 +4144,7 @@ type ListSandboxesRequest struct { func (x *ListSandboxesRequest) Reset() { *x = ListSandboxesRequest{} - mi := &file_openshell_proto_msgTypes[43] + mi := &file_openshell_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4043,7 +4156,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[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4056,7 +4169,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{45} } func (x *ListSandboxesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4099,7 +4212,7 @@ type ListSandboxProvidersRequest struct { func (x *ListSandboxProvidersRequest) Reset() { *x = ListSandboxProvidersRequest{} - mi := &file_openshell_proto_msgTypes[44] + mi := &file_openshell_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4111,7 +4224,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[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4124,7 +4237,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{46} } func (x *ListSandboxProvidersRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4163,7 +4276,7 @@ type AttachSandboxProviderRequest struct { func (x *AttachSandboxProviderRequest) Reset() { *x = AttachSandboxProviderRequest{} - mi := &file_openshell_proto_msgTypes[45] + mi := &file_openshell_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4175,7 +4288,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[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4188,7 +4301,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{47} } func (x *AttachSandboxProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4248,7 +4361,7 @@ type DetachSandboxProviderRequest struct { func (x *DetachSandboxProviderRequest) Reset() { *x = DetachSandboxProviderRequest{} - mi := &file_openshell_proto_msgTypes[46] + mi := &file_openshell_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4260,7 +4373,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[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4273,7 +4386,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{48} } func (x *DetachSandboxProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4330,7 +4443,7 @@ type DeleteSandboxRequest struct { func (x *DeleteSandboxRequest) Reset() { *x = DeleteSandboxRequest{} - mi := &file_openshell_proto_msgTypes[47] + mi := &file_openshell_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4342,7 +4455,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[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4355,7 +4468,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{49} } func (x *DeleteSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4401,7 +4514,7 @@ type StopSandboxRequest struct { func (x *StopSandboxRequest) Reset() { *x = StopSandboxRequest{} - mi := &file_openshell_proto_msgTypes[48] + mi := &file_openshell_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4413,7 +4526,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[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4426,7 +4539,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{50} } func (x *StopSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4465,7 +4578,7 @@ type StartSandboxRequest struct { func (x *StartSandboxRequest) Reset() { *x = StartSandboxRequest{} - mi := &file_openshell_proto_msgTypes[49] + mi := &file_openshell_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4477,7 +4590,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[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4490,7 +4603,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{51} } func (x *StartSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -4527,7 +4640,7 @@ type SandboxResponse struct { func (x *SandboxResponse) Reset() { *x = SandboxResponse{} - mi := &file_openshell_proto_msgTypes[50] + mi := &file_openshell_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4539,7 +4652,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[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4552,7 +4665,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{52} } func (x *SandboxResponse) GetSandbox() *Sandbox { @@ -4581,7 +4694,7 @@ type ListSandboxesResponse struct { func (x *ListSandboxesResponse) Reset() { *x = ListSandboxesResponse{} - mi := &file_openshell_proto_msgTypes[51] + mi := &file_openshell_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4593,7 +4706,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[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4606,7 +4719,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{53} } func (x *ListSandboxesResponse) GetSandboxes() []*Sandbox { @@ -4633,7 +4746,7 @@ type ListSandboxProvidersResponse struct { func (x *ListSandboxProvidersResponse) Reset() { *x = ListSandboxProvidersResponse{} - mi := &file_openshell_proto_msgTypes[52] + mi := &file_openshell_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4645,7 +4758,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[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4658,7 +4771,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{54} } func (x *ListSandboxProvidersResponse) GetProviders() []*datamodelv1.Provider { @@ -4682,7 +4795,7 @@ type AttachSandboxProviderResponse struct { func (x *AttachSandboxProviderResponse) Reset() { *x = AttachSandboxProviderResponse{} - mi := &file_openshell_proto_msgTypes[53] + mi := &file_openshell_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4694,7 +4807,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[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4707,7 +4820,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{55} } func (x *AttachSandboxProviderResponse) GetSandbox() *Sandbox { @@ -4745,7 +4858,7 @@ type DetachSandboxProviderResponse struct { func (x *DetachSandboxProviderResponse) Reset() { *x = DetachSandboxProviderResponse{} - mi := &file_openshell_proto_msgTypes[54] + mi := &file_openshell_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4757,7 +4870,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[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4770,7 +4883,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{56} } func (x *DetachSandboxProviderResponse) GetSandbox() *Sandbox { @@ -4812,7 +4925,7 @@ type ProviderDesiredIdentity struct { func (x *ProviderDesiredIdentity) Reset() { *x = ProviderDesiredIdentity{} - mi := &file_openshell_proto_msgTypes[55] + mi := &file_openshell_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4824,7 +4937,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[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4837,7 +4950,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{57} } func (x *ProviderDesiredIdentity) GetSandboxId() string { @@ -4912,7 +5025,7 @@ type ConfigSnapshotRevision struct { func (x *ConfigSnapshotRevision) Reset() { *x = ConfigSnapshotRevision{} - mi := &file_openshell_proto_msgTypes[56] + mi := &file_openshell_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4924,7 +5037,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[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4937,7 +5050,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{58} } func (x *ConfigSnapshotRevision) GetComponent() isConfigSnapshotRevision_Component { @@ -5014,7 +5127,7 @@ type SandboxConfigRevision struct { func (x *SandboxConfigRevision) Reset() { *x = SandboxConfigRevision{} - mi := &file_openshell_proto_msgTypes[57] + mi := &file_openshell_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5026,7 +5139,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[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5039,7 +5152,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{59} } func (x *SandboxConfigRevision) GetConfigRevision() uint64 { @@ -5098,7 +5211,7 @@ type ConfigUpdateOperation struct { func (x *ConfigUpdateOperation) Reset() { *x = ConfigUpdateOperation{} - mi := &file_openshell_proto_msgTypes[58] + mi := &file_openshell_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5110,7 +5223,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[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5123,7 +5236,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{60} } func (x *ConfigUpdateOperation) GetOperationId() string { @@ -5213,7 +5326,7 @@ type ProviderMutationReceipt struct { func (x *ProviderMutationReceipt) Reset() { *x = ProviderMutationReceipt{} - mi := &file_openshell_proto_msgTypes[59] + mi := &file_openshell_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5225,7 +5338,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[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5238,7 +5351,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{61} } func (x *ProviderMutationReceipt) GetReceiptId() string { @@ -5314,7 +5427,7 @@ type ProviderReadinessObservation struct { func (x *ProviderReadinessObservation) Reset() { *x = ProviderReadinessObservation{} - mi := &file_openshell_proto_msgTypes[60] + mi := &file_openshell_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5326,7 +5439,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[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5339,7 +5452,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{62} } func (x *ProviderReadinessObservation) GetSessionId() string { @@ -5438,7 +5551,7 @@ type ProviderReadinessStatus struct { func (x *ProviderReadinessStatus) Reset() { *x = ProviderReadinessStatus{} - mi := &file_openshell_proto_msgTypes[61] + mi := &file_openshell_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5450,7 +5563,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[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5463,7 +5576,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{63} } func (x *ProviderReadinessStatus) GetReceipt() *ProviderMutationReceipt { @@ -5537,7 +5650,7 @@ type GetSandboxProviderStatusRequest struct { func (x *GetSandboxProviderStatusRequest) Reset() { *x = GetSandboxProviderStatusRequest{} - mi := &file_openshell_proto_msgTypes[62] + mi := &file_openshell_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5549,7 +5662,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[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5562,7 +5675,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{64} } func (x *GetSandboxProviderStatusRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -5602,7 +5715,7 @@ type GetSandboxProviderStatusResponse struct { func (x *GetSandboxProviderStatusResponse) Reset() { *x = GetSandboxProviderStatusResponse{} - mi := &file_openshell_proto_msgTypes[63] + mi := &file_openshell_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5614,7 +5727,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[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5627,7 +5740,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{65} } func (x *GetSandboxProviderStatusResponse) GetStatus() *ProviderReadinessStatus { @@ -5649,7 +5762,7 @@ type ReportProviderReadinessRequest struct { func (x *ReportProviderReadinessRequest) Reset() { *x = ReportProviderReadinessRequest{} - mi := &file_openshell_proto_msgTypes[64] + mi := &file_openshell_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5661,7 +5774,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[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5674,7 +5787,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{66} } func (x *ReportProviderReadinessRequest) GetSandboxId() string { @@ -5704,7 +5817,7 @@ type ReportProviderReadinessResponse struct { func (x *ReportProviderReadinessResponse) Reset() { *x = ReportProviderReadinessResponse{} - mi := &file_openshell_proto_msgTypes[65] + mi := &file_openshell_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5716,7 +5829,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[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5729,7 +5842,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{67} } func (x *ReportProviderReadinessResponse) GetAcceptedSequence() uint64 { @@ -5766,7 +5879,7 @@ type DeleteSandboxResponse struct { func (x *DeleteSandboxResponse) Reset() { *x = DeleteSandboxResponse{} - mi := &file_openshell_proto_msgTypes[66] + mi := &file_openshell_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5778,7 +5891,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[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5791,7 +5904,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{68} } func (x *DeleteSandboxResponse) GetOutcome() DeletionOutcome { @@ -5820,7 +5933,7 @@ type CreateSshSessionRequest struct { func (x *CreateSshSessionRequest) Reset() { *x = CreateSshSessionRequest{} - mi := &file_openshell_proto_msgTypes[67] + mi := &file_openshell_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5832,7 +5945,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[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5845,7 +5958,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{69} } func (x *CreateSshSessionRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -5895,7 +6008,7 @@ type CreateSshSessionResponse struct { func (x *CreateSshSessionResponse) Reset() { *x = CreateSshSessionResponse{} - mi := &file_openshell_proto_msgTypes[68] + mi := &file_openshell_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5907,7 +6020,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[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5920,7 +6033,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{70} } func (x *CreateSshSessionResponse) GetSandboxId() string { @@ -5993,7 +6106,7 @@ type ExposeServiceRequest struct { func (x *ExposeServiceRequest) Reset() { *x = ExposeServiceRequest{} - mi := &file_openshell_proto_msgTypes[69] + mi := &file_openshell_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6005,7 +6118,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[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6018,7 +6131,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{71} } func (x *ExposeServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -6077,7 +6190,7 @@ type GetServiceRequest struct { func (x *GetServiceRequest) Reset() { *x = GetServiceRequest{} - mi := &file_openshell_proto_msgTypes[70] + mi := &file_openshell_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6089,7 +6202,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[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6102,7 +6215,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{72} } func (x *GetServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -6145,7 +6258,7 @@ type ListServicesRequest struct { func (x *ListServicesRequest) Reset() { *x = ListServicesRequest{} - mi := &file_openshell_proto_msgTypes[71] + mi := &file_openshell_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6157,7 +6270,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[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6170,7 +6283,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{73} } func (x *ListServicesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -6213,7 +6326,7 @@ type ListServicesResponse struct { func (x *ListServicesResponse) Reset() { *x = ListServicesResponse{} - mi := &file_openshell_proto_msgTypes[72] + mi := &file_openshell_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6225,7 +6338,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[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6238,7 +6351,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{74} } func (x *ListServicesResponse) GetServices() []*ServiceEndpointResponse { @@ -6273,7 +6386,7 @@ type DeleteServiceRequest struct { func (x *DeleteServiceRequest) Reset() { *x = DeleteServiceRequest{} - mi := &file_openshell_proto_msgTypes[73] + mi := &file_openshell_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6285,7 +6398,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[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6298,7 +6411,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{75} } func (x *DeleteServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -6346,7 +6459,7 @@ type DeleteServiceResponse struct { func (x *DeleteServiceResponse) Reset() { *x = DeleteServiceResponse{} - mi := &file_openshell_proto_msgTypes[74] + mi := &file_openshell_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6358,7 +6471,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[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6371,7 +6484,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{76} } func (x *DeleteServiceResponse) GetOutcome() DeletionOutcome { @@ -6402,7 +6515,7 @@ type ServiceEndpoint struct { func (x *ServiceEndpoint) Reset() { *x = ServiceEndpoint{} - mi := &file_openshell_proto_msgTypes[75] + mi := &file_openshell_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6414,7 +6527,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[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6427,7 +6540,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{77} } func (x *ServiceEndpoint) GetMetadata() *datamodelv1.ObjectMeta { @@ -6483,7 +6596,7 @@ type ServiceEndpointResponse struct { func (x *ServiceEndpointResponse) Reset() { *x = ServiceEndpointResponse{} - mi := &file_openshell_proto_msgTypes[76] + mi := &file_openshell_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6495,7 +6608,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[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6508,7 +6621,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{78} } func (x *ServiceEndpointResponse) GetEndpoint() *ServiceEndpoint { @@ -6539,7 +6652,7 @@ type RevokeSshSessionRequest struct { func (x *RevokeSshSessionRequest) Reset() { *x = RevokeSshSessionRequest{} - mi := &file_openshell_proto_msgTypes[77] + mi := &file_openshell_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6551,7 +6664,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[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6564,7 +6677,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{79} } func (x *RevokeSshSessionRequest) GetToken() string { @@ -6591,7 +6704,7 @@ type RevokeSshSessionResponse struct { func (x *RevokeSshSessionResponse) Reset() { *x = RevokeSshSessionResponse{} - mi := &file_openshell_proto_msgTypes[78] + mi := &file_openshell_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6603,7 +6716,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[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6616,7 +6729,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{80} } func (x *RevokeSshSessionResponse) GetOutcome() DeletionOutcome { @@ -6661,7 +6774,7 @@ type ExecSandboxRequest struct { func (x *ExecSandboxRequest) Reset() { *x = ExecSandboxRequest{} - mi := &file_openshell_proto_msgTypes[79] + mi := &file_openshell_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6673,7 +6786,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[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6686,7 +6799,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{81} } func (x *ExecSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -6776,7 +6889,7 @@ type ExecSandboxStdout struct { func (x *ExecSandboxStdout) Reset() { *x = ExecSandboxStdout{} - mi := &file_openshell_proto_msgTypes[80] + mi := &file_openshell_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6788,7 +6901,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[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6801,7 +6914,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{82} } func (x *ExecSandboxStdout) GetData() []byte { @@ -6821,7 +6934,7 @@ type ExecSandboxStderr struct { func (x *ExecSandboxStderr) Reset() { *x = ExecSandboxStderr{} - mi := &file_openshell_proto_msgTypes[81] + mi := &file_openshell_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6833,7 +6946,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[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6846,7 +6959,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{83} } func (x *ExecSandboxStderr) GetData() []byte { @@ -6866,7 +6979,7 @@ type ExecSandboxExit struct { func (x *ExecSandboxExit) Reset() { *x = ExecSandboxExit{} - mi := &file_openshell_proto_msgTypes[82] + mi := &file_openshell_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6878,7 +6991,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[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6891,7 +7004,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{84} } func (x *ExecSandboxExit) GetExitCode() int32 { @@ -6916,7 +7029,7 @@ type ExecSandboxEvent struct { func (x *ExecSandboxEvent) Reset() { *x = ExecSandboxEvent{} - mi := &file_openshell_proto_msgTypes[83] + mi := &file_openshell_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6928,7 +7041,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[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6941,7 +7054,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{85} } func (x *ExecSandboxEvent) GetPayload() isExecSandboxEvent_Payload { @@ -7023,7 +7136,7 @@ type TcpForwardInit struct { func (x *TcpForwardInit) Reset() { *x = TcpForwardInit{} - mi := &file_openshell_proto_msgTypes[84] + mi := &file_openshell_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7035,7 +7148,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[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7048,7 +7161,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{86} } func (x *TcpForwardInit) GetSandbox() string { @@ -7134,7 +7247,7 @@ type TcpForwardFrame struct { func (x *TcpForwardFrame) Reset() { *x = TcpForwardFrame{} - mi := &file_openshell_proto_msgTypes[85] + mi := &file_openshell_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7146,7 +7259,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[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7159,7 +7272,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{87} } func (x *TcpForwardFrame) GetPayload() isTcpForwardFrame_Payload { @@ -7218,7 +7331,7 @@ type ExecSandboxInput struct { func (x *ExecSandboxInput) Reset() { *x = ExecSandboxInput{} - mi := &file_openshell_proto_msgTypes[86] + mi := &file_openshell_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7230,7 +7343,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[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7243,7 +7356,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{88} } func (x *ExecSandboxInput) GetPayload() isExecSandboxInput_Payload { @@ -7316,7 +7429,7 @@ type ExecSandboxWindowResize struct { func (x *ExecSandboxWindowResize) Reset() { *x = ExecSandboxWindowResize{} - mi := &file_openshell_proto_msgTypes[87] + mi := &file_openshell_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7328,7 +7441,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[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7341,7 +7454,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{89} } func (x *ExecSandboxWindowResize) GetCols() uint32 { @@ -7377,7 +7490,7 @@ type SshSession struct { func (x *SshSession) Reset() { *x = SshSession{} - mi := &file_openshell_proto_msgTypes[88] + mi := &file_openshell_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7389,7 +7502,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[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7402,7 +7515,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{90} } func (x *SshSession) GetMetadata() *datamodelv1.ObjectMeta { @@ -7473,7 +7586,7 @@ type WatchSandboxRequest struct { func (x *WatchSandboxRequest) Reset() { *x = WatchSandboxRequest{} - mi := &file_openshell_proto_msgTypes[89] + mi := &file_openshell_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7485,7 +7598,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[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7498,7 +7611,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{91} } func (x *WatchSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -7595,7 +7708,7 @@ type SandboxStreamEvent struct { func (x *SandboxStreamEvent) Reset() { *x = SandboxStreamEvent{} - mi := &file_openshell_proto_msgTypes[90] + mi := &file_openshell_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7607,7 +7720,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[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7620,7 +7733,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{92} } func (x *SandboxStreamEvent) GetPayload() isSandboxStreamEvent_Payload { @@ -7733,7 +7846,7 @@ type SandboxLogLine struct { func (x *SandboxLogLine) Reset() { *x = SandboxLogLine{} - mi := &file_openshell_proto_msgTypes[91] + mi := &file_openshell_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7745,7 +7858,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[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7758,7 +7871,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{93} } func (x *SandboxLogLine) GetSandboxId() string { @@ -7819,7 +7932,7 @@ type SandboxStreamWarning struct { func (x *SandboxStreamWarning) Reset() { *x = SandboxStreamWarning{} - mi := &file_openshell_proto_msgTypes[92] + mi := &file_openshell_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7831,7 +7944,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[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7844,7 +7957,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{94} } func (x *SandboxStreamWarning) GetMessage() string { @@ -7869,7 +7982,7 @@ type CreateProviderRequest struct { func (x *CreateProviderRequest) Reset() { *x = CreateProviderRequest{} - mi := &file_openshell_proto_msgTypes[93] + mi := &file_openshell_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7881,7 +7994,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[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7894,7 +8007,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{95} } func (x *CreateProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -7930,7 +8043,7 @@ type GetProviderRequest struct { func (x *GetProviderRequest) Reset() { *x = GetProviderRequest{} - mi := &file_openshell_proto_msgTypes[94] + mi := &file_openshell_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7942,7 +8055,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[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7955,7 +8068,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{96} } func (x *GetProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -7989,7 +8102,7 @@ type ListProvidersRequest struct { func (x *ListProvidersRequest) Reset() { *x = ListProvidersRequest{} - mi := &file_openshell_proto_msgTypes[95] + mi := &file_openshell_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8001,7 +8114,7 @@ func (x *ListProvidersRequest) String() string { func (*ListProvidersRequest) ProtoMessage() {} func (x *ListProvidersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[95] + mi := &file_openshell_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8014,7 +8127,7 @@ func (x *ListProvidersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProvidersRequest.ProtoReflect.Descriptor instead. func (*ListProvidersRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{95} + return file_openshell_proto_rawDescGZIP(), []int{97} } func (x *ListProvidersRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -8059,7 +8172,7 @@ type UpdateProviderRequest struct { func (x *UpdateProviderRequest) Reset() { *x = UpdateProviderRequest{} - mi := &file_openshell_proto_msgTypes[96] + mi := &file_openshell_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8071,7 +8184,7 @@ func (x *UpdateProviderRequest) String() string { func (*UpdateProviderRequest) ProtoMessage() {} func (x *UpdateProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[96] + mi := &file_openshell_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8084,7 +8197,7 @@ func (x *UpdateProviderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderRequest.ProtoReflect.Descriptor instead. func (*UpdateProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{96} + return file_openshell_proto_rawDescGZIP(), []int{98} } func (x *UpdateProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -8138,7 +8251,7 @@ type DeleteProviderRequest struct { func (x *DeleteProviderRequest) Reset() { *x = DeleteProviderRequest{} - mi := &file_openshell_proto_msgTypes[97] + mi := &file_openshell_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8150,7 +8263,7 @@ func (x *DeleteProviderRequest) String() string { func (*DeleteProviderRequest) ProtoMessage() {} func (x *DeleteProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[97] + mi := &file_openshell_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8163,7 +8276,7 @@ func (x *DeleteProviderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{97} + return file_openshell_proto_rawDescGZIP(), []int{99} } func (x *DeleteProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -8209,7 +8322,7 @@ type ProviderResponse struct { func (x *ProviderResponse) Reset() { *x = ProviderResponse{} - mi := &file_openshell_proto_msgTypes[98] + mi := &file_openshell_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8221,7 +8334,7 @@ func (x *ProviderResponse) String() string { func (*ProviderResponse) ProtoMessage() {} func (x *ProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[98] + mi := &file_openshell_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8234,7 +8347,7 @@ func (x *ProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderResponse.ProtoReflect.Descriptor instead. func (*ProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{98} + return file_openshell_proto_rawDescGZIP(), []int{100} } func (x *ProviderResponse) GetProvider() *datamodelv1.Provider { @@ -8270,7 +8383,7 @@ type ListProvidersResponse struct { func (x *ListProvidersResponse) Reset() { *x = ListProvidersResponse{} - mi := &file_openshell_proto_msgTypes[99] + mi := &file_openshell_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8282,7 +8395,7 @@ func (x *ListProvidersResponse) String() string { func (*ListProvidersResponse) ProtoMessage() {} func (x *ListProvidersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[99] + mi := &file_openshell_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8295,7 +8408,7 @@ func (x *ListProvidersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProvidersResponse.ProtoReflect.Descriptor instead. func (*ListProvidersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{99} + return file_openshell_proto_rawDescGZIP(), []int{101} } func (x *ListProvidersResponse) GetProviders() []*datamodelv1.Provider { @@ -8329,7 +8442,7 @@ type ListProviderProfilesRequest struct { func (x *ListProviderProfilesRequest) Reset() { *x = ListProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[100] + mi := &file_openshell_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8341,7 +8454,7 @@ func (x *ListProviderProfilesRequest) String() string { func (*ListProviderProfilesRequest) ProtoMessage() {} func (x *ListProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[100] + mi := &file_openshell_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8354,7 +8467,7 @@ func (x *ListProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*ListProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{100} + return file_openshell_proto_rawDescGZIP(), []int{102} } func (x *ListProviderProfilesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -8390,7 +8503,7 @@ type GetProviderProfileRequest struct { func (x *GetProviderProfileRequest) Reset() { *x = GetProviderProfileRequest{} - mi := &file_openshell_proto_msgTypes[101] + mi := &file_openshell_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8402,7 +8515,7 @@ func (x *GetProviderProfileRequest) String() string { func (*GetProviderProfileRequest) ProtoMessage() {} func (x *GetProviderProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[101] + mi := &file_openshell_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8415,7 +8528,7 @@ func (x *GetProviderProfileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderProfileRequest.ProtoReflect.Descriptor instead. func (*GetProviderProfileRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{101} + return file_openshell_proto_rawDescGZIP(), []int{103} } func (x *GetProviderProfileRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -8443,7 +8556,7 @@ type ProviderProfileImportItem struct { func (x *ProviderProfileImportItem) Reset() { *x = ProviderProfileImportItem{} - mi := &file_openshell_proto_msgTypes[102] + mi := &file_openshell_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8455,7 +8568,7 @@ func (x *ProviderProfileImportItem) String() string { func (*ProviderProfileImportItem) ProtoMessage() {} func (x *ProviderProfileImportItem) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[102] + mi := &file_openshell_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8468,7 +8581,7 @@ func (x *ProviderProfileImportItem) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileImportItem.ProtoReflect.Descriptor instead. func (*ProviderProfileImportItem) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{102} + return file_openshell_proto_rawDescGZIP(), []int{104} } func (x *ProviderProfileImportItem) GetProfile() *ProviderProfile { @@ -8499,7 +8612,7 @@ type ProviderProfileDiagnostic struct { func (x *ProviderProfileDiagnostic) Reset() { *x = ProviderProfileDiagnostic{} - mi := &file_openshell_proto_msgTypes[103] + mi := &file_openshell_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8511,7 +8624,7 @@ func (x *ProviderProfileDiagnostic) String() string { func (*ProviderProfileDiagnostic) ProtoMessage() {} func (x *ProviderProfileDiagnostic) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[103] + mi := &file_openshell_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8524,7 +8637,7 @@ func (x *ProviderProfileDiagnostic) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileDiagnostic.ProtoReflect.Descriptor instead. func (*ProviderProfileDiagnostic) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{103} + return file_openshell_proto_rawDescGZIP(), []int{105} } func (x *ProviderProfileDiagnostic) GetSource() string { @@ -8581,7 +8694,7 @@ type ProviderCredentialTokenGrantAudienceOverride struct { func (x *ProviderCredentialTokenGrantAudienceOverride) Reset() { *x = ProviderCredentialTokenGrantAudienceOverride{} - mi := &file_openshell_proto_msgTypes[104] + mi := &file_openshell_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8593,7 +8706,7 @@ func (x *ProviderCredentialTokenGrantAudienceOverride) String() string { func (*ProviderCredentialTokenGrantAudienceOverride) ProtoMessage() {} func (x *ProviderCredentialTokenGrantAudienceOverride) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[104] + mi := &file_openshell_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8606,7 +8719,7 @@ func (x *ProviderCredentialTokenGrantAudienceOverride) ProtoReflect() protorefle // Deprecated: Use ProviderCredentialTokenGrantAudienceOverride.ProtoReflect.Descriptor instead. func (*ProviderCredentialTokenGrantAudienceOverride) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{104} + return file_openshell_proto_rawDescGZIP(), []int{106} } func (x *ProviderCredentialTokenGrantAudienceOverride) GetHost() string { @@ -8660,7 +8773,7 @@ type ProviderCredentialTokenGrantSubjectToken struct { func (x *ProviderCredentialTokenGrantSubjectToken) Reset() { *x = ProviderCredentialTokenGrantSubjectToken{} - mi := &file_openshell_proto_msgTypes[105] + mi := &file_openshell_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8672,7 +8785,7 @@ func (x *ProviderCredentialTokenGrantSubjectToken) String() string { func (*ProviderCredentialTokenGrantSubjectToken) ProtoMessage() {} func (x *ProviderCredentialTokenGrantSubjectToken) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[105] + mi := &file_openshell_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8685,7 +8798,7 @@ func (x *ProviderCredentialTokenGrantSubjectToken) ProtoReflect() protoreflect.M // Deprecated: Use ProviderCredentialTokenGrantSubjectToken.ProtoReflect.Descriptor instead. func (*ProviderCredentialTokenGrantSubjectToken) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{105} + return file_openshell_proto_rawDescGZIP(), []int{107} } func (x *ProviderCredentialTokenGrantSubjectToken) GetSource() string { @@ -8741,7 +8854,7 @@ type ProviderCredentialTokenGrant struct { func (x *ProviderCredentialTokenGrant) Reset() { *x = ProviderCredentialTokenGrant{} - mi := &file_openshell_proto_msgTypes[106] + mi := &file_openshell_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8753,7 +8866,7 @@ func (x *ProviderCredentialTokenGrant) String() string { func (*ProviderCredentialTokenGrant) ProtoMessage() {} func (x *ProviderCredentialTokenGrant) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[106] + mi := &file_openshell_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8766,7 +8879,7 @@ func (x *ProviderCredentialTokenGrant) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialTokenGrant.ProtoReflect.Descriptor instead. func (*ProviderCredentialTokenGrant) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{106} + return file_openshell_proto_rawDescGZIP(), []int{108} } func (x *ProviderCredentialTokenGrant) GetTokenEndpoint() string { @@ -8858,7 +8971,7 @@ type ProviderProfileCredential struct { func (x *ProviderProfileCredential) Reset() { *x = ProviderProfileCredential{} - mi := &file_openshell_proto_msgTypes[107] + mi := &file_openshell_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8870,7 +8983,7 @@ func (x *ProviderProfileCredential) String() string { func (*ProviderProfileCredential) ProtoMessage() {} func (x *ProviderProfileCredential) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[107] + mi := &file_openshell_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8883,7 +8996,7 @@ func (x *ProviderProfileCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileCredential.ProtoReflect.Descriptor instead. func (*ProviderProfileCredential) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{107} + return file_openshell_proto_rawDescGZIP(), []int{109} } func (x *ProviderProfileCredential) GetName() string { @@ -8968,7 +9081,7 @@ type ProviderCredentialRefreshMaterial struct { func (x *ProviderCredentialRefreshMaterial) Reset() { *x = ProviderCredentialRefreshMaterial{} - mi := &file_openshell_proto_msgTypes[108] + mi := &file_openshell_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8980,7 +9093,7 @@ func (x *ProviderCredentialRefreshMaterial) String() string { func (*ProviderCredentialRefreshMaterial) ProtoMessage() {} func (x *ProviderCredentialRefreshMaterial) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[108] + mi := &file_openshell_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8993,7 +9106,7 @@ func (x *ProviderCredentialRefreshMaterial) ProtoReflect() protoreflect.Message // Deprecated: Use ProviderCredentialRefreshMaterial.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefreshMaterial) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{108} + return file_openshell_proto_rawDescGZIP(), []int{110} } func (x *ProviderCredentialRefreshMaterial) GetName() string { @@ -9038,7 +9151,7 @@ type ProviderCredentialRefreshOutput struct { func (x *ProviderCredentialRefreshOutput) Reset() { *x = ProviderCredentialRefreshOutput{} - mi := &file_openshell_proto_msgTypes[109] + mi := &file_openshell_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9050,7 +9163,7 @@ func (x *ProviderCredentialRefreshOutput) String() string { func (*ProviderCredentialRefreshOutput) ProtoMessage() {} func (x *ProviderCredentialRefreshOutput) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[109] + mi := &file_openshell_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9063,7 +9176,7 @@ func (x *ProviderCredentialRefreshOutput) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialRefreshOutput.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefreshOutput) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{109} + return file_openshell_proto_rawDescGZIP(), []int{111} } func (x *ProviderCredentialRefreshOutput) GetOutput() string { @@ -9095,7 +9208,7 @@ type ProviderCredentialRefresh struct { func (x *ProviderCredentialRefresh) Reset() { *x = ProviderCredentialRefresh{} - mi := &file_openshell_proto_msgTypes[110] + mi := &file_openshell_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9107,7 +9220,7 @@ func (x *ProviderCredentialRefresh) String() string { func (*ProviderCredentialRefresh) ProtoMessage() {} func (x *ProviderCredentialRefresh) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[110] + mi := &file_openshell_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9120,7 +9233,7 @@ func (x *ProviderCredentialRefresh) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialRefresh.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefresh) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{110} + return file_openshell_proto_rawDescGZIP(), []int{112} } func (x *ProviderCredentialRefresh) GetStrategy() ProviderCredentialRefreshStrategy { @@ -9201,7 +9314,7 @@ type ProviderCredentialRefreshStatus struct { func (x *ProviderCredentialRefreshStatus) Reset() { *x = ProviderCredentialRefreshStatus{} - mi := &file_openshell_proto_msgTypes[111] + mi := &file_openshell_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9213,7 +9326,7 @@ func (x *ProviderCredentialRefreshStatus) String() string { func (*ProviderCredentialRefreshStatus) ProtoMessage() {} func (x *ProviderCredentialRefreshStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[111] + mi := &file_openshell_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9226,7 +9339,7 @@ func (x *ProviderCredentialRefreshStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialRefreshStatus.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefreshStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{111} + return file_openshell_proto_rawDescGZIP(), []int{113} } func (x *ProviderCredentialRefreshStatus) GetProvider() string { @@ -9331,7 +9444,7 @@ type ProviderProfileDiscovery struct { func (x *ProviderProfileDiscovery) Reset() { *x = ProviderProfileDiscovery{} - mi := &file_openshell_proto_msgTypes[112] + mi := &file_openshell_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9343,7 +9456,7 @@ func (x *ProviderProfileDiscovery) String() string { func (*ProviderProfileDiscovery) ProtoMessage() {} func (x *ProviderProfileDiscovery) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[112] + mi := &file_openshell_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9356,7 +9469,7 @@ func (x *ProviderProfileDiscovery) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileDiscovery.ProtoReflect.Descriptor instead. func (*ProviderProfileDiscovery) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{112} + return file_openshell_proto_rawDescGZIP(), []int{114} } func (x *ProviderProfileDiscovery) GetCredentials() []string { @@ -9378,7 +9491,7 @@ type GetProviderRefreshStatusRequest struct { func (x *GetProviderRefreshStatusRequest) Reset() { *x = GetProviderRefreshStatusRequest{} - mi := &file_openshell_proto_msgTypes[113] + mi := &file_openshell_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9390,7 +9503,7 @@ func (x *GetProviderRefreshStatusRequest) String() string { func (*GetProviderRefreshStatusRequest) ProtoMessage() {} func (x *GetProviderRefreshStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[113] + mi := &file_openshell_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9403,7 +9516,7 @@ func (x *GetProviderRefreshStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderRefreshStatusRequest.ProtoReflect.Descriptor instead. func (*GetProviderRefreshStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{113} + return file_openshell_proto_rawDescGZIP(), []int{115} } func (x *GetProviderRefreshStatusRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -9436,7 +9549,7 @@ type GetProviderRefreshStatusResponse struct { func (x *GetProviderRefreshStatusResponse) Reset() { *x = GetProviderRefreshStatusResponse{} - mi := &file_openshell_proto_msgTypes[114] + mi := &file_openshell_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9448,7 +9561,7 @@ func (x *GetProviderRefreshStatusResponse) String() string { func (*GetProviderRefreshStatusResponse) ProtoMessage() {} func (x *GetProviderRefreshStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[114] + mi := &file_openshell_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9461,7 +9574,7 @@ func (x *GetProviderRefreshStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderRefreshStatusResponse.ProtoReflect.Descriptor instead. func (*GetProviderRefreshStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{114} + return file_openshell_proto_rawDescGZIP(), []int{116} } func (x *GetProviderRefreshStatusResponse) GetCredentials() []*ProviderCredentialRefreshStatus { @@ -9493,7 +9606,7 @@ type ConfigureProviderRefreshRequest struct { func (x *ConfigureProviderRefreshRequest) Reset() { *x = ConfigureProviderRefreshRequest{} - mi := &file_openshell_proto_msgTypes[115] + mi := &file_openshell_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9505,7 +9618,7 @@ func (x *ConfigureProviderRefreshRequest) String() string { func (*ConfigureProviderRefreshRequest) ProtoMessage() {} func (x *ConfigureProviderRefreshRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[115] + mi := &file_openshell_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9518,7 +9631,7 @@ func (x *ConfigureProviderRefreshRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigureProviderRefreshRequest.ProtoReflect.Descriptor instead. func (*ConfigureProviderRefreshRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{115} + return file_openshell_proto_rawDescGZIP(), []int{117} } func (x *ConfigureProviderRefreshRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -9586,7 +9699,7 @@ type ConfigureProviderRefreshResponse struct { func (x *ConfigureProviderRefreshResponse) Reset() { *x = ConfigureProviderRefreshResponse{} - mi := &file_openshell_proto_msgTypes[116] + mi := &file_openshell_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9598,7 +9711,7 @@ func (x *ConfigureProviderRefreshResponse) String() string { func (*ConfigureProviderRefreshResponse) ProtoMessage() {} func (x *ConfigureProviderRefreshResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[116] + mi := &file_openshell_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9611,7 +9724,7 @@ func (x *ConfigureProviderRefreshResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigureProviderRefreshResponse.ProtoReflect.Descriptor instead. func (*ConfigureProviderRefreshResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{116} + return file_openshell_proto_rawDescGZIP(), []int{118} } func (x *ConfigureProviderRefreshResponse) GetStatus() *ProviderCredentialRefreshStatus { @@ -9636,7 +9749,7 @@ type RotateProviderCredentialRequest struct { func (x *RotateProviderCredentialRequest) Reset() { *x = RotateProviderCredentialRequest{} - mi := &file_openshell_proto_msgTypes[117] + mi := &file_openshell_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9648,7 +9761,7 @@ func (x *RotateProviderCredentialRequest) String() string { func (*RotateProviderCredentialRequest) ProtoMessage() {} func (x *RotateProviderCredentialRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[117] + mi := &file_openshell_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9661,7 +9774,7 @@ func (x *RotateProviderCredentialRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RotateProviderCredentialRequest.ProtoReflect.Descriptor instead. func (*RotateProviderCredentialRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{117} + return file_openshell_proto_rawDescGZIP(), []int{119} } func (x *RotateProviderCredentialRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -9701,7 +9814,7 @@ type RotateProviderCredentialResponse struct { func (x *RotateProviderCredentialResponse) Reset() { *x = RotateProviderCredentialResponse{} - mi := &file_openshell_proto_msgTypes[118] + mi := &file_openshell_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9713,7 +9826,7 @@ func (x *RotateProviderCredentialResponse) String() string { func (*RotateProviderCredentialResponse) ProtoMessage() {} func (x *RotateProviderCredentialResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[118] + mi := &file_openshell_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9726,7 +9839,7 @@ func (x *RotateProviderCredentialResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RotateProviderCredentialResponse.ProtoReflect.Descriptor instead. func (*RotateProviderCredentialResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{118} + return file_openshell_proto_rawDescGZIP(), []int{120} } func (x *RotateProviderCredentialResponse) GetStatus() *ProviderCredentialRefreshStatus { @@ -9752,7 +9865,7 @@ type DeleteProviderRefreshRequest struct { func (x *DeleteProviderRefreshRequest) Reset() { *x = DeleteProviderRefreshRequest{} - mi := &file_openshell_proto_msgTypes[119] + mi := &file_openshell_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9764,7 +9877,7 @@ func (x *DeleteProviderRefreshRequest) String() string { func (*DeleteProviderRefreshRequest) ProtoMessage() {} func (x *DeleteProviderRefreshRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[119] + mi := &file_openshell_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9777,7 +9890,7 @@ func (x *DeleteProviderRefreshRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRefreshRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderRefreshRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{119} + return file_openshell_proto_rawDescGZIP(), []int{121} } func (x *DeleteProviderRefreshRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -9824,7 +9937,7 @@ type DeleteProviderRefreshResponse struct { func (x *DeleteProviderRefreshResponse) Reset() { *x = DeleteProviderRefreshResponse{} - mi := &file_openshell_proto_msgTypes[120] + mi := &file_openshell_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9836,7 +9949,7 @@ func (x *DeleteProviderRefreshResponse) String() string { func (*DeleteProviderRefreshResponse) ProtoMessage() {} func (x *DeleteProviderRefreshResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[120] + mi := &file_openshell_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9849,7 +9962,7 @@ func (x *DeleteProviderRefreshResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRefreshResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderRefreshResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{120} + return file_openshell_proto_rawDescGZIP(), []int{122} } func (x *DeleteProviderRefreshResponse) GetOutcome() DeletionOutcome { @@ -9889,7 +10002,7 @@ type ProviderProfile struct { func (x *ProviderProfile) Reset() { *x = ProviderProfile{} - mi := &file_openshell_proto_msgTypes[121] + mi := &file_openshell_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9901,7 +10014,7 @@ func (x *ProviderProfile) String() string { func (*ProviderProfile) ProtoMessage() {} func (x *ProviderProfile) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[121] + mi := &file_openshell_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9914,7 +10027,7 @@ func (x *ProviderProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfile.ProtoReflect.Descriptor instead. func (*ProviderProfile) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{121} + return file_openshell_proto_rawDescGZIP(), []int{123} } func (x *ProviderProfile) GetId() string { @@ -10018,7 +10131,7 @@ type ProviderProfileResponse struct { func (x *ProviderProfileResponse) Reset() { *x = ProviderProfileResponse{} - mi := &file_openshell_proto_msgTypes[122] + mi := &file_openshell_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10030,7 +10143,7 @@ func (x *ProviderProfileResponse) String() string { func (*ProviderProfileResponse) ProtoMessage() {} func (x *ProviderProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[122] + mi := &file_openshell_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10043,7 +10156,7 @@ func (x *ProviderProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileResponse.ProtoReflect.Descriptor instead. func (*ProviderProfileResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{122} + return file_openshell_proto_rawDescGZIP(), []int{124} } func (x *ProviderProfileResponse) GetProfile() *ProviderProfile { @@ -10065,7 +10178,7 @@ type ListProviderProfilesResponse struct { func (x *ListProviderProfilesResponse) Reset() { *x = ListProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[123] + mi := &file_openshell_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10077,7 +10190,7 @@ func (x *ListProviderProfilesResponse) String() string { func (*ListProviderProfilesResponse) ProtoMessage() {} func (x *ListProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[123] + mi := &file_openshell_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10090,7 +10203,7 @@ func (x *ListProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*ListProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{123} + return file_openshell_proto_rawDescGZIP(), []int{125} } func (x *ListProviderProfilesResponse) GetProfiles() []*ProviderProfile { @@ -10122,7 +10235,7 @@ type ImportProviderProfilesRequest struct { func (x *ImportProviderProfilesRequest) Reset() { *x = ImportProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[124] + mi := &file_openshell_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10134,7 +10247,7 @@ func (x *ImportProviderProfilesRequest) String() string { func (*ImportProviderProfilesRequest) ProtoMessage() {} func (x *ImportProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[124] + mi := &file_openshell_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10147,7 +10260,7 @@ func (x *ImportProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*ImportProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{124} + return file_openshell_proto_rawDescGZIP(), []int{126} } func (x *ImportProviderProfilesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10183,7 +10296,7 @@ type ImportProviderProfilesResponse struct { func (x *ImportProviderProfilesResponse) Reset() { *x = ImportProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[125] + mi := &file_openshell_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10195,7 +10308,7 @@ func (x *ImportProviderProfilesResponse) String() string { func (*ImportProviderProfilesResponse) ProtoMessage() {} func (x *ImportProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[125] + mi := &file_openshell_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10208,7 +10321,7 @@ func (x *ImportProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*ImportProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{125} + return file_openshell_proto_rawDescGZIP(), []int{127} } func (x *ImportProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -10254,7 +10367,7 @@ type UpdateProviderProfilesRequest struct { func (x *UpdateProviderProfilesRequest) Reset() { *x = UpdateProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[126] + mi := &file_openshell_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10266,7 +10379,7 @@ func (x *UpdateProviderProfilesRequest) String() string { func (*UpdateProviderProfilesRequest) ProtoMessage() {} func (x *UpdateProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[126] + mi := &file_openshell_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10279,7 +10392,7 @@ func (x *UpdateProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*UpdateProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{126} + return file_openshell_proto_rawDescGZIP(), []int{128} } func (x *UpdateProviderProfilesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10329,7 +10442,7 @@ type UpdateProviderProfilesResponse struct { func (x *UpdateProviderProfilesResponse) Reset() { *x = UpdateProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[127] + mi := &file_openshell_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10341,7 +10454,7 @@ func (x *UpdateProviderProfilesResponse) String() string { func (*UpdateProviderProfilesResponse) ProtoMessage() {} func (x *UpdateProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[127] + mi := &file_openshell_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10354,7 +10467,7 @@ func (x *UpdateProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*UpdateProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{127} + return file_openshell_proto_rawDescGZIP(), []int{129} } func (x *UpdateProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -10390,7 +10503,7 @@ type LintProviderProfilesRequest struct { func (x *LintProviderProfilesRequest) Reset() { *x = LintProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[128] + mi := &file_openshell_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10402,7 +10515,7 @@ func (x *LintProviderProfilesRequest) String() string { func (*LintProviderProfilesRequest) ProtoMessage() {} func (x *LintProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[128] + mi := &file_openshell_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10415,7 +10528,7 @@ func (x *LintProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LintProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*LintProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{128} + return file_openshell_proto_rawDescGZIP(), []int{130} } func (x *LintProviderProfilesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10443,7 +10556,7 @@ type LintProviderProfilesResponse struct { func (x *LintProviderProfilesResponse) Reset() { *x = LintProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[129] + mi := &file_openshell_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10455,7 +10568,7 @@ func (x *LintProviderProfilesResponse) String() string { func (*LintProviderProfilesResponse) ProtoMessage() {} func (x *LintProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[129] + mi := &file_openshell_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10468,7 +10581,7 @@ func (x *LintProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LintProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*LintProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{129} + return file_openshell_proto_rawDescGZIP(), []int{131} } func (x *LintProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -10495,7 +10608,7 @@ type DeleteProviderResponse struct { func (x *DeleteProviderResponse) Reset() { *x = DeleteProviderResponse{} - mi := &file_openshell_proto_msgTypes[130] + mi := &file_openshell_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10507,7 +10620,7 @@ func (x *DeleteProviderResponse) String() string { func (*DeleteProviderResponse) ProtoMessage() {} func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[130] + mi := &file_openshell_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10520,7 +10633,7 @@ func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{130} + return file_openshell_proto_rawDescGZIP(), []int{132} } func (x *DeleteProviderResponse) GetOutcome() DeletionOutcome { @@ -10546,7 +10659,7 @@ type DeleteProviderProfileRequest struct { func (x *DeleteProviderProfileRequest) Reset() { *x = DeleteProviderProfileRequest{} - mi := &file_openshell_proto_msgTypes[131] + mi := &file_openshell_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10558,7 +10671,7 @@ func (x *DeleteProviderProfileRequest) String() string { func (*DeleteProviderProfileRequest) ProtoMessage() {} func (x *DeleteProviderProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[131] + mi := &file_openshell_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10571,7 +10684,7 @@ func (x *DeleteProviderProfileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderProfileRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderProfileRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{131} + return file_openshell_proto_rawDescGZIP(), []int{133} } func (x *DeleteProviderProfileRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10612,7 +10725,7 @@ type DeleteProviderProfileResponse struct { func (x *DeleteProviderProfileResponse) Reset() { *x = DeleteProviderProfileResponse{} - mi := &file_openshell_proto_msgTypes[132] + mi := &file_openshell_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10624,7 +10737,7 @@ func (x *DeleteProviderProfileResponse) String() string { func (*DeleteProviderProfileResponse) ProtoMessage() {} func (x *DeleteProviderProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[132] + mi := &file_openshell_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10637,7 +10750,7 @@ func (x *DeleteProviderProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderProfileResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderProfileResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{132} + return file_openshell_proto_rawDescGZIP(), []int{134} } func (x *DeleteProviderProfileResponse) GetOutcome() DeletionOutcome { @@ -10662,7 +10775,7 @@ type GetSandboxProviderEnvironmentRequest struct { func (x *GetSandboxProviderEnvironmentRequest) Reset() { *x = GetSandboxProviderEnvironmentRequest{} - mi := &file_openshell_proto_msgTypes[133] + mi := &file_openshell_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10674,7 +10787,7 @@ func (x *GetSandboxProviderEnvironmentRequest) String() string { func (*GetSandboxProviderEnvironmentRequest) ProtoMessage() {} func (x *GetSandboxProviderEnvironmentRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[133] + mi := &file_openshell_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10687,7 +10800,7 @@ func (x *GetSandboxProviderEnvironmentRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use GetSandboxProviderEnvironmentRequest.ProtoReflect.Descriptor instead. func (*GetSandboxProviderEnvironmentRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{133} + return file_openshell_proto_rawDescGZIP(), []int{135} } func (x *GetSandboxProviderEnvironmentRequest) GetSandboxId() string { @@ -10716,7 +10829,7 @@ type StaticCredentialEndpointBinding struct { func (x *StaticCredentialEndpointBinding) Reset() { *x = StaticCredentialEndpointBinding{} - mi := &file_openshell_proto_msgTypes[134] + mi := &file_openshell_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10728,7 +10841,7 @@ func (x *StaticCredentialEndpointBinding) String() string { func (*StaticCredentialEndpointBinding) ProtoMessage() {} func (x *StaticCredentialEndpointBinding) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[134] + mi := &file_openshell_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10741,7 +10854,7 @@ func (x *StaticCredentialEndpointBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticCredentialEndpointBinding.ProtoReflect.Descriptor instead. func (*StaticCredentialEndpointBinding) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{134} + return file_openshell_proto_rawDescGZIP(), []int{136} } func (x *StaticCredentialEndpointBinding) GetHost() string { @@ -10785,7 +10898,7 @@ type StaticCredentialBinding struct { func (x *StaticCredentialBinding) Reset() { *x = StaticCredentialBinding{} - mi := &file_openshell_proto_msgTypes[135] + mi := &file_openshell_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10797,7 +10910,7 @@ func (x *StaticCredentialBinding) String() string { func (*StaticCredentialBinding) ProtoMessage() {} func (x *StaticCredentialBinding) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[135] + mi := &file_openshell_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10810,7 +10923,7 @@ func (x *StaticCredentialBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticCredentialBinding.ProtoReflect.Descriptor instead. func (*StaticCredentialBinding) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{135} + return file_openshell_proto_rawDescGZIP(), []int{137} } func (x *StaticCredentialBinding) GetEndpoints() []*StaticCredentialEndpointBinding { @@ -10867,7 +10980,7 @@ type GetSandboxProviderEnvironmentResponse struct { func (x *GetSandboxProviderEnvironmentResponse) Reset() { *x = GetSandboxProviderEnvironmentResponse{} - mi := &file_openshell_proto_msgTypes[136] + mi := &file_openshell_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10879,7 +10992,7 @@ func (x *GetSandboxProviderEnvironmentResponse) String() string { func (*GetSandboxProviderEnvironmentResponse) ProtoMessage() {} func (x *GetSandboxProviderEnvironmentResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[136] + mi := &file_openshell_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10892,7 +11005,7 @@ func (x *GetSandboxProviderEnvironmentResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use GetSandboxProviderEnvironmentResponse.ProtoReflect.Descriptor instead. func (*GetSandboxProviderEnvironmentResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{136} + return file_openshell_proto_rawDescGZIP(), []int{138} } func (x *GetSandboxProviderEnvironmentResponse) GetEnvironment() map[string]string { @@ -10975,7 +11088,7 @@ type ExchangeProviderSubjectTokenRequest struct { func (x *ExchangeProviderSubjectTokenRequest) Reset() { *x = ExchangeProviderSubjectTokenRequest{} - mi := &file_openshell_proto_msgTypes[137] + mi := &file_openshell_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10987,7 +11100,7 @@ func (x *ExchangeProviderSubjectTokenRequest) String() string { func (*ExchangeProviderSubjectTokenRequest) ProtoMessage() {} func (x *ExchangeProviderSubjectTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[137] + mi := &file_openshell_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11000,7 +11113,7 @@ func (x *ExchangeProviderSubjectTokenRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use ExchangeProviderSubjectTokenRequest.ProtoReflect.Descriptor instead. func (*ExchangeProviderSubjectTokenRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{137} + return file_openshell_proto_rawDescGZIP(), []int{139} } func (x *ExchangeProviderSubjectTokenRequest) GetSandboxId() string { @@ -11042,7 +11155,7 @@ type ExchangeProviderSubjectTokenResponse struct { func (x *ExchangeProviderSubjectTokenResponse) Reset() { *x = ExchangeProviderSubjectTokenResponse{} - mi := &file_openshell_proto_msgTypes[138] + mi := &file_openshell_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11054,7 +11167,7 @@ func (x *ExchangeProviderSubjectTokenResponse) String() string { func (*ExchangeProviderSubjectTokenResponse) ProtoMessage() {} func (x *ExchangeProviderSubjectTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[138] + mi := &file_openshell_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11067,7 +11180,7 @@ func (x *ExchangeProviderSubjectTokenResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use ExchangeProviderSubjectTokenResponse.ProtoReflect.Descriptor instead. func (*ExchangeProviderSubjectTokenResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{138} + return file_openshell_proto_rawDescGZIP(), []int{140} } func (x *ExchangeProviderSubjectTokenResponse) GetAccessToken() string { @@ -11140,7 +11253,7 @@ type UpdateConfigRequest struct { func (x *UpdateConfigRequest) Reset() { *x = UpdateConfigRequest{} - mi := &file_openshell_proto_msgTypes[139] + mi := &file_openshell_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11152,7 +11265,7 @@ func (x *UpdateConfigRequest) String() string { func (*UpdateConfigRequest) ProtoMessage() {} func (x *UpdateConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[139] + mi := &file_openshell_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11165,7 +11278,7 @@ func (x *UpdateConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateConfigRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{139} + return file_openshell_proto_rawDescGZIP(), []int{141} } func (x *UpdateConfigRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -11262,7 +11375,7 @@ type PolicyMergeOperation struct { func (x *PolicyMergeOperation) Reset() { *x = PolicyMergeOperation{} - mi := &file_openshell_proto_msgTypes[140] + mi := &file_openshell_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11274,7 +11387,7 @@ func (x *PolicyMergeOperation) String() string { func (*PolicyMergeOperation) ProtoMessage() {} func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[140] + mi := &file_openshell_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11287,7 +11400,7 @@ func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyMergeOperation.ProtoReflect.Descriptor instead. func (*PolicyMergeOperation) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{140} + return file_openshell_proto_rawDescGZIP(), []int{142} } func (x *PolicyMergeOperation) GetOperation() isPolicyMergeOperation_Operation { @@ -11401,7 +11514,7 @@ type AddNetworkRule struct { func (x *AddNetworkRule) Reset() { *x = AddNetworkRule{} - mi := &file_openshell_proto_msgTypes[141] + mi := &file_openshell_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11413,7 +11526,7 @@ func (x *AddNetworkRule) String() string { func (*AddNetworkRule) ProtoMessage() {} func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[141] + mi := &file_openshell_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11426,7 +11539,7 @@ func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { // Deprecated: Use AddNetworkRule.ProtoReflect.Descriptor instead. func (*AddNetworkRule) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{141} + return file_openshell_proto_rawDescGZIP(), []int{143} } func (x *AddNetworkRule) GetRuleName() string { @@ -11454,7 +11567,7 @@ type RemoveNetworkEndpoint struct { func (x *RemoveNetworkEndpoint) Reset() { *x = RemoveNetworkEndpoint{} - mi := &file_openshell_proto_msgTypes[142] + mi := &file_openshell_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11466,7 +11579,7 @@ func (x *RemoveNetworkEndpoint) String() string { func (*RemoveNetworkEndpoint) ProtoMessage() {} func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[142] + mi := &file_openshell_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11479,7 +11592,7 @@ func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkEndpoint.ProtoReflect.Descriptor instead. func (*RemoveNetworkEndpoint) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{142} + return file_openshell_proto_rawDescGZIP(), []int{144} } func (x *RemoveNetworkEndpoint) GetRuleName() string { @@ -11512,7 +11625,7 @@ type RemoveNetworkRule struct { func (x *RemoveNetworkRule) Reset() { *x = RemoveNetworkRule{} - mi := &file_openshell_proto_msgTypes[143] + mi := &file_openshell_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11524,7 +11637,7 @@ func (x *RemoveNetworkRule) String() string { func (*RemoveNetworkRule) ProtoMessage() {} func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[143] + mi := &file_openshell_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11537,7 +11650,7 @@ func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkRule.ProtoReflect.Descriptor instead. func (*RemoveNetworkRule) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{143} + return file_openshell_proto_rawDescGZIP(), []int{145} } func (x *RemoveNetworkRule) GetRuleName() string { @@ -11566,7 +11679,7 @@ type L7RuleTarget struct { func (x *L7RuleTarget) Reset() { *x = L7RuleTarget{} - mi := &file_openshell_proto_msgTypes[144] + mi := &file_openshell_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11578,7 +11691,7 @@ func (x *L7RuleTarget) String() string { func (*L7RuleTarget) ProtoMessage() {} func (x *L7RuleTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[144] + mi := &file_openshell_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11591,7 +11704,7 @@ func (x *L7RuleTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use L7RuleTarget.ProtoReflect.Descriptor instead. func (*L7RuleTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{144} + return file_openshell_proto_rawDescGZIP(), []int{146} } func (x *L7RuleTarget) GetRuleName() string { @@ -11646,7 +11759,7 @@ type AddDenyRules struct { func (x *AddDenyRules) Reset() { *x = AddDenyRules{} - mi := &file_openshell_proto_msgTypes[145] + mi := &file_openshell_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11658,7 +11771,7 @@ func (x *AddDenyRules) String() string { func (*AddDenyRules) ProtoMessage() {} func (x *AddDenyRules) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[145] + mi := &file_openshell_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11671,7 +11784,7 @@ func (x *AddDenyRules) ProtoReflect() protoreflect.Message { // Deprecated: Use AddDenyRules.ProtoReflect.Descriptor instead. func (*AddDenyRules) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{145} + return file_openshell_proto_rawDescGZIP(), []int{147} } func (x *AddDenyRules) GetDenyRules() []*sandboxv1.L7DenyRule { @@ -11698,7 +11811,7 @@ type AddAllowRules struct { func (x *AddAllowRules) Reset() { *x = AddAllowRules{} - mi := &file_openshell_proto_msgTypes[146] + mi := &file_openshell_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11710,7 +11823,7 @@ func (x *AddAllowRules) String() string { func (*AddAllowRules) ProtoMessage() {} func (x *AddAllowRules) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[146] + mi := &file_openshell_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11723,7 +11836,7 @@ func (x *AddAllowRules) ProtoReflect() protoreflect.Message { // Deprecated: Use AddAllowRules.ProtoReflect.Descriptor instead. func (*AddAllowRules) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{146} + return file_openshell_proto_rawDescGZIP(), []int{148} } func (x *AddAllowRules) GetRules() []*sandboxv1.L7Rule { @@ -11750,7 +11863,7 @@ type RemoveNetworkBinary struct { func (x *RemoveNetworkBinary) Reset() { *x = RemoveNetworkBinary{} - mi := &file_openshell_proto_msgTypes[147] + mi := &file_openshell_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11762,7 +11875,7 @@ func (x *RemoveNetworkBinary) String() string { func (*RemoveNetworkBinary) ProtoMessage() {} func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[147] + mi := &file_openshell_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11775,7 +11888,7 @@ func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkBinary.ProtoReflect.Descriptor instead. func (*RemoveNetworkBinary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{147} + return file_openshell_proto_rawDescGZIP(), []int{149} } func (x *RemoveNetworkBinary) GetRuleName() string { @@ -11811,7 +11924,7 @@ type UpdateConfigResponse struct { func (x *UpdateConfigResponse) Reset() { *x = UpdateConfigResponse{} - mi := &file_openshell_proto_msgTypes[148] + mi := &file_openshell_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11823,7 +11936,7 @@ func (x *UpdateConfigResponse) String() string { func (*UpdateConfigResponse) ProtoMessage() {} func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[148] + mi := &file_openshell_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11836,7 +11949,7 @@ func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateConfigResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{148} + return file_openshell_proto_rawDescGZIP(), []int{150} } func (x *UpdateConfigResponse) GetVersion() uint32 { @@ -11890,7 +12003,7 @@ type GetSandboxPolicyStatusRequest struct { func (x *GetSandboxPolicyStatusRequest) Reset() { *x = GetSandboxPolicyStatusRequest{} - mi := &file_openshell_proto_msgTypes[149] + mi := &file_openshell_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11902,7 +12015,7 @@ func (x *GetSandboxPolicyStatusRequest) String() string { func (*GetSandboxPolicyStatusRequest) ProtoMessage() {} func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[149] + mi := &file_openshell_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11915,7 +12028,7 @@ func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxPolicyStatusRequest.ProtoReflect.Descriptor instead. func (*GetSandboxPolicyStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{149} + return file_openshell_proto_rawDescGZIP(), []int{151} } func (x *GetSandboxPolicyStatusRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -11959,7 +12072,7 @@ type GetSandboxPolicyStatusResponse struct { func (x *GetSandboxPolicyStatusResponse) Reset() { *x = GetSandboxPolicyStatusResponse{} - mi := &file_openshell_proto_msgTypes[150] + mi := &file_openshell_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11971,7 +12084,7 @@ func (x *GetSandboxPolicyStatusResponse) String() string { func (*GetSandboxPolicyStatusResponse) ProtoMessage() {} func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[150] + mi := &file_openshell_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11984,7 +12097,7 @@ func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxPolicyStatusResponse.ProtoReflect.Descriptor instead. func (*GetSandboxPolicyStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{150} + return file_openshell_proto_rawDescGZIP(), []int{152} } func (x *GetSandboxPolicyStatusResponse) GetRevision() *SandboxPolicyRevision { @@ -12021,7 +12134,7 @@ type ListSandboxPoliciesRequest struct { func (x *ListSandboxPoliciesRequest) Reset() { *x = ListSandboxPoliciesRequest{} - mi := &file_openshell_proto_msgTypes[151] + mi := &file_openshell_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12033,7 +12146,7 @@ func (x *ListSandboxPoliciesRequest) String() string { func (*ListSandboxPoliciesRequest) ProtoMessage() {} func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[151] + mi := &file_openshell_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12046,7 +12159,7 @@ func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxPoliciesRequest.ProtoReflect.Descriptor instead. func (*ListSandboxPoliciesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{151} + return file_openshell_proto_rawDescGZIP(), []int{153} } func (x *ListSandboxPoliciesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -12098,7 +12211,7 @@ type ListSandboxPoliciesResponse struct { func (x *ListSandboxPoliciesResponse) Reset() { *x = ListSandboxPoliciesResponse{} - mi := &file_openshell_proto_msgTypes[152] + mi := &file_openshell_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12110,7 +12223,7 @@ func (x *ListSandboxPoliciesResponse) String() string { func (*ListSandboxPoliciesResponse) ProtoMessage() {} func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[152] + mi := &file_openshell_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12123,7 +12236,7 @@ func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxPoliciesResponse.ProtoReflect.Descriptor instead. func (*ListSandboxPoliciesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{152} + return file_openshell_proto_rawDescGZIP(), []int{154} } func (x *ListSandboxPoliciesResponse) GetRevisions() []*SandboxPolicyRevision { @@ -12157,7 +12270,7 @@ type ReportPolicyStatusRequest struct { func (x *ReportPolicyStatusRequest) Reset() { *x = ReportPolicyStatusRequest{} - mi := &file_openshell_proto_msgTypes[153] + mi := &file_openshell_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12169,7 +12282,7 @@ func (x *ReportPolicyStatusRequest) String() string { func (*ReportPolicyStatusRequest) ProtoMessage() {} func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[153] + mi := &file_openshell_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12182,7 +12295,7 @@ func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportPolicyStatusRequest.ProtoReflect.Descriptor instead. func (*ReportPolicyStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{153} + return file_openshell_proto_rawDescGZIP(), []int{155} } func (x *ReportPolicyStatusRequest) GetSandboxId() string { @@ -12222,7 +12335,7 @@ type ReportPolicyStatusResponse struct { func (x *ReportPolicyStatusResponse) Reset() { *x = ReportPolicyStatusResponse{} - mi := &file_openshell_proto_msgTypes[154] + mi := &file_openshell_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12234,7 +12347,7 @@ func (x *ReportPolicyStatusResponse) String() string { func (*ReportPolicyStatusResponse) ProtoMessage() {} func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[154] + mi := &file_openshell_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12247,7 +12360,7 @@ func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportPolicyStatusResponse.ProtoReflect.Descriptor instead. func (*ReportPolicyStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{154} + return file_openshell_proto_rawDescGZIP(), []int{156} } type SandboxConfigurationAdmission struct { @@ -12265,7 +12378,7 @@ type SandboxConfigurationAdmission struct { func (x *SandboxConfigurationAdmission) Reset() { *x = SandboxConfigurationAdmission{} - mi := &file_openshell_proto_msgTypes[155] + mi := &file_openshell_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12277,7 +12390,7 @@ func (x *SandboxConfigurationAdmission) String() string { func (*SandboxConfigurationAdmission) ProtoMessage() {} func (x *SandboxConfigurationAdmission) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[155] + mi := &file_openshell_proto_msgTypes[157] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12290,7 +12403,7 @@ func (x *SandboxConfigurationAdmission) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxConfigurationAdmission.ProtoReflect.Descriptor instead. func (*SandboxConfigurationAdmission) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{155} + return file_openshell_proto_rawDescGZIP(), []int{157} } func (x *SandboxConfigurationAdmission) GetInstanceId() string { @@ -12354,7 +12467,7 @@ type ReportSandboxConfigurationRequest struct { func (x *ReportSandboxConfigurationRequest) Reset() { *x = ReportSandboxConfigurationRequest{} - mi := &file_openshell_proto_msgTypes[156] + mi := &file_openshell_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12366,7 +12479,7 @@ func (x *ReportSandboxConfigurationRequest) String() string { func (*ReportSandboxConfigurationRequest) ProtoMessage() {} func (x *ReportSandboxConfigurationRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[156] + mi := &file_openshell_proto_msgTypes[158] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12379,7 +12492,7 @@ func (x *ReportSandboxConfigurationRequest) ProtoReflect() protoreflect.Message // Deprecated: Use ReportSandboxConfigurationRequest.ProtoReflect.Descriptor instead. func (*ReportSandboxConfigurationRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{156} + return file_openshell_proto_rawDescGZIP(), []int{158} } func (x *ReportSandboxConfigurationRequest) GetSandboxId() string { @@ -12411,7 +12524,7 @@ type ReportSandboxConfigurationResponse struct { func (x *ReportSandboxConfigurationResponse) Reset() { *x = ReportSandboxConfigurationResponse{} - mi := &file_openshell_proto_msgTypes[157] + mi := &file_openshell_proto_msgTypes[159] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12423,7 +12536,7 @@ func (x *ReportSandboxConfigurationResponse) String() string { func (*ReportSandboxConfigurationResponse) ProtoMessage() {} func (x *ReportSandboxConfigurationResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[157] + mi := &file_openshell_proto_msgTypes[159] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12436,7 +12549,7 @@ func (x *ReportSandboxConfigurationResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ReportSandboxConfigurationResponse.ProtoReflect.Descriptor instead. func (*ReportSandboxConfigurationResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{157} + return file_openshell_proto_rawDescGZIP(), []int{159} } // A versioned policy revision with metadata. @@ -12469,7 +12582,7 @@ type SandboxPolicyRevision struct { func (x *SandboxPolicyRevision) Reset() { *x = SandboxPolicyRevision{} - mi := &file_openshell_proto_msgTypes[158] + mi := &file_openshell_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12481,7 +12594,7 @@ func (x *SandboxPolicyRevision) String() string { func (*SandboxPolicyRevision) ProtoMessage() {} func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[158] + mi := &file_openshell_proto_msgTypes[160] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12494,7 +12607,7 @@ func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxPolicyRevision.ProtoReflect.Descriptor instead. func (*SandboxPolicyRevision) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{158} + return file_openshell_proto_rawDescGZIP(), []int{160} } func (x *SandboxPolicyRevision) GetVersion() uint32 { @@ -12574,7 +12687,7 @@ type GetSandboxLogsRequest struct { func (x *GetSandboxLogsRequest) Reset() { *x = GetSandboxLogsRequest{} - mi := &file_openshell_proto_msgTypes[159] + mi := &file_openshell_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12586,7 +12699,7 @@ func (x *GetSandboxLogsRequest) String() string { func (*GetSandboxLogsRequest) ProtoMessage() {} func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[159] + mi := &file_openshell_proto_msgTypes[161] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12599,7 +12712,7 @@ func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxLogsRequest.ProtoReflect.Descriptor instead. func (*GetSandboxLogsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{159} + return file_openshell_proto_rawDescGZIP(), []int{161} } func (x *GetSandboxLogsRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -12657,7 +12770,7 @@ type PushSandboxLogsRequest struct { func (x *PushSandboxLogsRequest) Reset() { *x = PushSandboxLogsRequest{} - mi := &file_openshell_proto_msgTypes[160] + mi := &file_openshell_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12669,7 +12782,7 @@ func (x *PushSandboxLogsRequest) String() string { func (*PushSandboxLogsRequest) ProtoMessage() {} func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[160] + mi := &file_openshell_proto_msgTypes[162] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12682,7 +12795,7 @@ func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PushSandboxLogsRequest.ProtoReflect.Descriptor instead. func (*PushSandboxLogsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{160} + return file_openshell_proto_rawDescGZIP(), []int{162} } func (x *PushSandboxLogsRequest) GetSandboxId() string { @@ -12708,7 +12821,7 @@ type PushSandboxLogsResponse struct { func (x *PushSandboxLogsResponse) Reset() { *x = PushSandboxLogsResponse{} - mi := &file_openshell_proto_msgTypes[161] + mi := &file_openshell_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12720,7 +12833,7 @@ func (x *PushSandboxLogsResponse) String() string { func (*PushSandboxLogsResponse) ProtoMessage() {} func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[161] + mi := &file_openshell_proto_msgTypes[163] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12733,7 +12846,7 @@ func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PushSandboxLogsResponse.ProtoReflect.Descriptor instead. func (*PushSandboxLogsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{161} + return file_openshell_proto_rawDescGZIP(), []int{163} } // Get sandbox logs response. @@ -12749,7 +12862,7 @@ type GetSandboxLogsResponse struct { func (x *GetSandboxLogsResponse) Reset() { *x = GetSandboxLogsResponse{} - mi := &file_openshell_proto_msgTypes[162] + mi := &file_openshell_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12761,7 +12874,7 @@ func (x *GetSandboxLogsResponse) String() string { func (*GetSandboxLogsResponse) ProtoMessage() {} func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[162] + mi := &file_openshell_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12774,7 +12887,7 @@ func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxLogsResponse.ProtoReflect.Descriptor instead. func (*GetSandboxLogsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{162} + return file_openshell_proto_rawDescGZIP(), []int{164} } func (x *GetSandboxLogsResponse) GetLogs() []*SandboxLogLine { @@ -12807,7 +12920,7 @@ type SupervisorMessage struct { func (x *SupervisorMessage) Reset() { *x = SupervisorMessage{} - mi := &file_openshell_proto_msgTypes[163] + mi := &file_openshell_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12819,7 +12932,7 @@ func (x *SupervisorMessage) String() string { func (*SupervisorMessage) ProtoMessage() {} func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[163] + mi := &file_openshell_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12832,7 +12945,7 @@ func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorMessage.ProtoReflect.Descriptor instead. func (*SupervisorMessage) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{163} + return file_openshell_proto_rawDescGZIP(), []int{165} } func (x *SupervisorMessage) GetPayload() isSupervisorMessage_Payload { @@ -12923,7 +13036,7 @@ type GatewayMessage struct { func (x *GatewayMessage) Reset() { *x = GatewayMessage{} - mi := &file_openshell_proto_msgTypes[164] + mi := &file_openshell_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12935,7 +13048,7 @@ func (x *GatewayMessage) String() string { func (*GatewayMessage) ProtoMessage() {} func (x *GatewayMessage) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[164] + mi := &file_openshell_proto_msgTypes[166] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12948,7 +13061,7 @@ func (x *GatewayMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayMessage.ProtoReflect.Descriptor instead. func (*GatewayMessage) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{164} + return file_openshell_proto_rawDescGZIP(), []int{166} } func (x *GatewayMessage) GetPayload() isGatewayMessage_Payload { @@ -13052,7 +13165,7 @@ type SupervisorHello struct { func (x *SupervisorHello) Reset() { *x = SupervisorHello{} - mi := &file_openshell_proto_msgTypes[165] + mi := &file_openshell_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13064,7 +13177,7 @@ func (x *SupervisorHello) String() string { func (*SupervisorHello) ProtoMessage() {} func (x *SupervisorHello) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[165] + mi := &file_openshell_proto_msgTypes[167] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13077,7 +13190,7 @@ func (x *SupervisorHello) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorHello.ProtoReflect.Descriptor instead. func (*SupervisorHello) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{165} + return file_openshell_proto_rawDescGZIP(), []int{167} } func (x *SupervisorHello) GetSandboxId() string { @@ -13114,7 +13227,7 @@ type SessionAccepted struct { func (x *SessionAccepted) Reset() { *x = SessionAccepted{} - mi := &file_openshell_proto_msgTypes[166] + mi := &file_openshell_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13126,7 +13239,7 @@ func (x *SessionAccepted) String() string { func (*SessionAccepted) ProtoMessage() {} func (x *SessionAccepted) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[166] + mi := &file_openshell_proto_msgTypes[168] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13139,7 +13252,7 @@ func (x *SessionAccepted) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionAccepted.ProtoReflect.Descriptor instead. func (*SessionAccepted) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{166} + return file_openshell_proto_rawDescGZIP(), []int{168} } func (x *SessionAccepted) GetSessionId() string { @@ -13167,7 +13280,7 @@ type SessionRejected struct { func (x *SessionRejected) Reset() { *x = SessionRejected{} - mi := &file_openshell_proto_msgTypes[167] + mi := &file_openshell_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13179,7 +13292,7 @@ func (x *SessionRejected) String() string { func (*SessionRejected) ProtoMessage() {} func (x *SessionRejected) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[167] + mi := &file_openshell_proto_msgTypes[169] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13192,7 +13305,7 @@ func (x *SessionRejected) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionRejected.ProtoReflect.Descriptor instead. func (*SessionRejected) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{167} + return file_openshell_proto_rawDescGZIP(), []int{169} } func (x *SessionRejected) GetReason() string { @@ -13211,7 +13324,7 @@ type SupervisorHeartbeat struct { func (x *SupervisorHeartbeat) Reset() { *x = SupervisorHeartbeat{} - mi := &file_openshell_proto_msgTypes[168] + mi := &file_openshell_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13223,7 +13336,7 @@ func (x *SupervisorHeartbeat) String() string { func (*SupervisorHeartbeat) ProtoMessage() {} func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[168] + mi := &file_openshell_proto_msgTypes[170] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13236,7 +13349,7 @@ func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorHeartbeat.ProtoReflect.Descriptor instead. func (*SupervisorHeartbeat) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{168} + return file_openshell_proto_rawDescGZIP(), []int{170} } // Gateway heartbeat. @@ -13248,7 +13361,7 @@ type GatewayHeartbeat struct { func (x *GatewayHeartbeat) Reset() { *x = GatewayHeartbeat{} - mi := &file_openshell_proto_msgTypes[169] + mi := &file_openshell_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13260,7 +13373,7 @@ func (x *GatewayHeartbeat) String() string { func (*GatewayHeartbeat) ProtoMessage() {} func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[169] + mi := &file_openshell_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13273,7 +13386,7 @@ func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayHeartbeat.ProtoReflect.Descriptor instead. func (*GatewayHeartbeat) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{169} + return file_openshell_proto_rawDescGZIP(), []int{171} } // Terminal result reported before the supervisor shuts down. A successful RPC @@ -13290,7 +13403,7 @@ type ReportMainProcessExitRequest struct { func (x *ReportMainProcessExitRequest) Reset() { *x = ReportMainProcessExitRequest{} - mi := &file_openshell_proto_msgTypes[170] + mi := &file_openshell_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13302,7 +13415,7 @@ func (x *ReportMainProcessExitRequest) String() string { func (*ReportMainProcessExitRequest) ProtoMessage() {} func (x *ReportMainProcessExitRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[170] + mi := &file_openshell_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13315,7 +13428,7 @@ func (x *ReportMainProcessExitRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportMainProcessExitRequest.ProtoReflect.Descriptor instead. func (*ReportMainProcessExitRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{170} + return file_openshell_proto_rawDescGZIP(), []int{172} } func (x *ReportMainProcessExitRequest) GetSandboxId() string { @@ -13347,7 +13460,7 @@ type ReportMainProcessExitResponse struct { func (x *ReportMainProcessExitResponse) Reset() { *x = ReportMainProcessExitResponse{} - mi := &file_openshell_proto_msgTypes[171] + mi := &file_openshell_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13359,7 +13472,7 @@ func (x *ReportMainProcessExitResponse) String() string { func (*ReportMainProcessExitResponse) ProtoMessage() {} func (x *ReportMainProcessExitResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[171] + mi := &file_openshell_proto_msgTypes[173] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13372,7 +13485,7 @@ func (x *ReportMainProcessExitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportMainProcessExitResponse.ProtoReflect.Descriptor instead. func (*ReportMainProcessExitResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{171} + return file_openshell_proto_rawDescGZIP(), []int{173} } // Terminal-delivery completion reported after all expected foreground SSH @@ -13387,7 +13500,7 @@ type FinalizeMainProcessExitRequest struct { func (x *FinalizeMainProcessExitRequest) Reset() { *x = FinalizeMainProcessExitRequest{} - mi := &file_openshell_proto_msgTypes[172] + mi := &file_openshell_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13399,7 +13512,7 @@ func (x *FinalizeMainProcessExitRequest) String() string { func (*FinalizeMainProcessExitRequest) ProtoMessage() {} func (x *FinalizeMainProcessExitRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[172] + mi := &file_openshell_proto_msgTypes[174] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13412,7 +13525,7 @@ func (x *FinalizeMainProcessExitRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizeMainProcessExitRequest.ProtoReflect.Descriptor instead. func (*FinalizeMainProcessExitRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{172} + return file_openshell_proto_rawDescGZIP(), []int{174} } func (x *FinalizeMainProcessExitRequest) GetSandboxId() string { @@ -13437,7 +13550,7 @@ type FinalizeMainProcessExitResponse struct { func (x *FinalizeMainProcessExitResponse) Reset() { *x = FinalizeMainProcessExitResponse{} - mi := &file_openshell_proto_msgTypes[173] + mi := &file_openshell_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13449,7 +13562,7 @@ func (x *FinalizeMainProcessExitResponse) String() string { func (*FinalizeMainProcessExitResponse) ProtoMessage() {} func (x *FinalizeMainProcessExitResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[173] + mi := &file_openshell_proto_msgTypes[175] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13462,7 +13575,7 @@ func (x *FinalizeMainProcessExitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizeMainProcessExitResponse.ProtoReflect.Descriptor instead. func (*FinalizeMainProcessExitResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{173} + return file_openshell_proto_rawDescGZIP(), []int{175} } // Gateway requests the supervisor to open a relay channel. @@ -13491,7 +13604,7 @@ type RelayOpen struct { func (x *RelayOpen) Reset() { *x = RelayOpen{} - mi := &file_openshell_proto_msgTypes[174] + mi := &file_openshell_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13503,7 +13616,7 @@ func (x *RelayOpen) String() string { func (*RelayOpen) ProtoMessage() {} func (x *RelayOpen) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[174] + mi := &file_openshell_proto_msgTypes[176] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13516,7 +13629,7 @@ func (x *RelayOpen) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpen.ProtoReflect.Descriptor instead. func (*RelayOpen) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{174} + return file_openshell_proto_rawDescGZIP(), []int{176} } func (x *RelayOpen) GetChannelId() string { @@ -13583,7 +13696,7 @@ type SshRelayTarget struct { func (x *SshRelayTarget) Reset() { *x = SshRelayTarget{} - mi := &file_openshell_proto_msgTypes[175] + mi := &file_openshell_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13595,7 +13708,7 @@ func (x *SshRelayTarget) String() string { func (*SshRelayTarget) ProtoMessage() {} func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[175] + mi := &file_openshell_proto_msgTypes[177] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13608,7 +13721,7 @@ func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use SshRelayTarget.ProtoReflect.Descriptor instead. func (*SshRelayTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{175} + return file_openshell_proto_rawDescGZIP(), []int{177} } // TCP target dialed by the supervisor from inside the sandbox. @@ -13624,7 +13737,7 @@ type TcpRelayTarget struct { func (x *TcpRelayTarget) Reset() { *x = TcpRelayTarget{} - mi := &file_openshell_proto_msgTypes[176] + mi := &file_openshell_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13636,7 +13749,7 @@ func (x *TcpRelayTarget) String() string { func (*TcpRelayTarget) ProtoMessage() {} func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[176] + mi := &file_openshell_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13649,7 +13762,7 @@ func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use TcpRelayTarget.ProtoReflect.Descriptor instead. func (*TcpRelayTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{176} + return file_openshell_proto_rawDescGZIP(), []int{178} } func (x *TcpRelayTarget) GetHost() string { @@ -13677,7 +13790,7 @@ type RelayInit struct { func (x *RelayInit) Reset() { *x = RelayInit{} - mi := &file_openshell_proto_msgTypes[177] + mi := &file_openshell_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13689,7 +13802,7 @@ func (x *RelayInit) String() string { func (*RelayInit) ProtoMessage() {} func (x *RelayInit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[177] + mi := &file_openshell_proto_msgTypes[179] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13702,7 +13815,7 @@ func (x *RelayInit) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayInit.ProtoReflect.Descriptor instead. func (*RelayInit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{177} + return file_openshell_proto_rawDescGZIP(), []int{179} } func (x *RelayInit) GetChannelId() string { @@ -13729,7 +13842,7 @@ type RelayFrame struct { func (x *RelayFrame) Reset() { *x = RelayFrame{} - mi := &file_openshell_proto_msgTypes[178] + mi := &file_openshell_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13741,7 +13854,7 @@ func (x *RelayFrame) String() string { func (*RelayFrame) ProtoMessage() {} func (x *RelayFrame) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[178] + mi := &file_openshell_proto_msgTypes[180] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13754,7 +13867,7 @@ func (x *RelayFrame) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayFrame.ProtoReflect.Descriptor instead. func (*RelayFrame) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{178} + return file_openshell_proto_rawDescGZIP(), []int{180} } func (x *RelayFrame) GetPayload() isRelayFrame_Payload { @@ -13813,7 +13926,7 @@ type RelayOpenResult struct { func (x *RelayOpenResult) Reset() { *x = RelayOpenResult{} - mi := &file_openshell_proto_msgTypes[179] + mi := &file_openshell_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13825,7 +13938,7 @@ func (x *RelayOpenResult) String() string { func (*RelayOpenResult) ProtoMessage() {} func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[179] + mi := &file_openshell_proto_msgTypes[181] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13838,7 +13951,7 @@ func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpenResult.ProtoReflect.Descriptor instead. func (*RelayOpenResult) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{179} + return file_openshell_proto_rawDescGZIP(), []int{181} } func (x *RelayOpenResult) GetChannelId() string { @@ -13875,7 +13988,7 @@ type RelayClose struct { func (x *RelayClose) Reset() { *x = RelayClose{} - mi := &file_openshell_proto_msgTypes[180] + mi := &file_openshell_proto_msgTypes[182] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13887,7 +14000,7 @@ func (x *RelayClose) String() string { func (*RelayClose) ProtoMessage() {} func (x *RelayClose) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[180] + mi := &file_openshell_proto_msgTypes[182] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13900,7 +14013,7 @@ func (x *RelayClose) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayClose.ProtoReflect.Descriptor instead. func (*RelayClose) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{180} + return file_openshell_proto_rawDescGZIP(), []int{182} } func (x *RelayClose) GetChannelId() string { @@ -13934,7 +14047,7 @@ type L7RequestSample struct { func (x *L7RequestSample) Reset() { *x = L7RequestSample{} - mi := &file_openshell_proto_msgTypes[181] + mi := &file_openshell_proto_msgTypes[183] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13946,7 +14059,7 @@ func (x *L7RequestSample) String() string { func (*L7RequestSample) ProtoMessage() {} func (x *L7RequestSample) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[181] + mi := &file_openshell_proto_msgTypes[183] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13959,7 +14072,7 @@ func (x *L7RequestSample) ProtoReflect() protoreflect.Message { // Deprecated: Use L7RequestSample.ProtoReflect.Descriptor instead. func (*L7RequestSample) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{181} + return file_openshell_proto_rawDescGZIP(), []int{183} } func (x *L7RequestSample) GetMethod() string { @@ -14033,7 +14146,7 @@ type DenialSummary struct { func (x *DenialSummary) Reset() { *x = DenialSummary{} - mi := &file_openshell_proto_msgTypes[182] + mi := &file_openshell_proto_msgTypes[184] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14045,7 +14158,7 @@ func (x *DenialSummary) String() string { func (*DenialSummary) ProtoMessage() {} func (x *DenialSummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[182] + mi := &file_openshell_proto_msgTypes[184] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14058,7 +14171,7 @@ func (x *DenialSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialSummary.ProtoReflect.Descriptor instead. func (*DenialSummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{182} + return file_openshell_proto_rawDescGZIP(), []int{184} } func (x *DenialSummary) GetSandboxId() string { @@ -14193,7 +14306,7 @@ type DenialGroupCount struct { func (x *DenialGroupCount) Reset() { *x = DenialGroupCount{} - mi := &file_openshell_proto_msgTypes[183] + mi := &file_openshell_proto_msgTypes[185] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14205,7 +14318,7 @@ func (x *DenialGroupCount) String() string { func (*DenialGroupCount) ProtoMessage() {} func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[183] + mi := &file_openshell_proto_msgTypes[185] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14218,7 +14331,7 @@ func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialGroupCount.ProtoReflect.Descriptor instead. func (*DenialGroupCount) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{183} + return file_openshell_proto_rawDescGZIP(), []int{185} } func (x *DenialGroupCount) GetDenyGroup() string { @@ -14251,7 +14364,7 @@ type NetworkActivitySummary struct { func (x *NetworkActivitySummary) Reset() { *x = NetworkActivitySummary{} - mi := &file_openshell_proto_msgTypes[184] + mi := &file_openshell_proto_msgTypes[186] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14263,7 +14376,7 @@ func (x *NetworkActivitySummary) String() string { func (*NetworkActivitySummary) ProtoMessage() {} func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[184] + mi := &file_openshell_proto_msgTypes[186] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14276,7 +14389,7 @@ func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkActivitySummary.ProtoReflect.Descriptor instead. func (*NetworkActivitySummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{184} + return file_openshell_proto_rawDescGZIP(), []int{186} } func (x *NetworkActivitySummary) GetNetworkActivityCount() uint32 { @@ -14364,7 +14477,7 @@ type PolicyChunk struct { func (x *PolicyChunk) Reset() { *x = PolicyChunk{} - mi := &file_openshell_proto_msgTypes[185] + mi := &file_openshell_proto_msgTypes[187] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14376,7 +14489,7 @@ func (x *PolicyChunk) String() string { func (*PolicyChunk) ProtoMessage() {} func (x *PolicyChunk) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[185] + mi := &file_openshell_proto_msgTypes[187] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14389,7 +14502,7 @@ func (x *PolicyChunk) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyChunk.ProtoReflect.Descriptor instead. func (*PolicyChunk) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{185} + return file_openshell_proto_rawDescGZIP(), []int{187} } func (x *PolicyChunk) GetId() string { @@ -14577,7 +14690,7 @@ type DraftPolicyUpdate struct { func (x *DraftPolicyUpdate) Reset() { *x = DraftPolicyUpdate{} - mi := &file_openshell_proto_msgTypes[186] + mi := &file_openshell_proto_msgTypes[188] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14589,7 +14702,7 @@ func (x *DraftPolicyUpdate) String() string { func (*DraftPolicyUpdate) ProtoMessage() {} func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[186] + mi := &file_openshell_proto_msgTypes[188] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14602,7 +14715,7 @@ func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftPolicyUpdate.ProtoReflect.Descriptor instead. func (*DraftPolicyUpdate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{186} + return file_openshell_proto_rawDescGZIP(), []int{188} } func (x *DraftPolicyUpdate) GetDraftVersion() uint64 { @@ -14661,7 +14774,7 @@ type SubmitPolicyAnalysisRequest struct { func (x *SubmitPolicyAnalysisRequest) Reset() { *x = SubmitPolicyAnalysisRequest{} - mi := &file_openshell_proto_msgTypes[187] + mi := &file_openshell_proto_msgTypes[189] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14673,7 +14786,7 @@ func (x *SubmitPolicyAnalysisRequest) String() string { func (*SubmitPolicyAnalysisRequest) ProtoMessage() {} func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[187] + mi := &file_openshell_proto_msgTypes[189] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14686,7 +14799,7 @@ func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisRequest.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{187} + return file_openshell_proto_rawDescGZIP(), []int{189} } func (x *SubmitPolicyAnalysisRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -14749,7 +14862,7 @@ type SubmitPolicyAnalysisResponse struct { func (x *SubmitPolicyAnalysisResponse) Reset() { *x = SubmitPolicyAnalysisResponse{} - mi := &file_openshell_proto_msgTypes[188] + mi := &file_openshell_proto_msgTypes[190] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14761,7 +14874,7 @@ func (x *SubmitPolicyAnalysisResponse) String() string { func (*SubmitPolicyAnalysisResponse) ProtoMessage() {} func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[188] + mi := &file_openshell_proto_msgTypes[190] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14774,7 +14887,7 @@ func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisResponse.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{188} + return file_openshell_proto_rawDescGZIP(), []int{190} } func (x *SubmitPolicyAnalysisResponse) GetAcceptedChunks() uint32 { @@ -14819,7 +14932,7 @@ type GetDraftPolicyRequest struct { func (x *GetDraftPolicyRequest) Reset() { *x = GetDraftPolicyRequest{} - mi := &file_openshell_proto_msgTypes[189] + mi := &file_openshell_proto_msgTypes[191] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14831,7 +14944,7 @@ func (x *GetDraftPolicyRequest) String() string { func (*GetDraftPolicyRequest) ProtoMessage() {} func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[189] + mi := &file_openshell_proto_msgTypes[191] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14844,7 +14957,7 @@ func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyRequest.ProtoReflect.Descriptor instead. func (*GetDraftPolicyRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{189} + return file_openshell_proto_rawDescGZIP(), []int{191} } func (x *GetDraftPolicyRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -14884,7 +14997,7 @@ type GetDraftPolicyResponse struct { func (x *GetDraftPolicyResponse) Reset() { *x = GetDraftPolicyResponse{} - mi := &file_openshell_proto_msgTypes[190] + mi := &file_openshell_proto_msgTypes[192] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14896,7 +15009,7 @@ func (x *GetDraftPolicyResponse) String() string { func (*GetDraftPolicyResponse) ProtoMessage() {} func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[190] + mi := &file_openshell_proto_msgTypes[192] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14909,7 +15022,7 @@ func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyResponse.ProtoReflect.Descriptor instead. func (*GetDraftPolicyResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{190} + return file_openshell_proto_rawDescGZIP(), []int{192} } func (x *GetDraftPolicyResponse) GetChunks() []*PolicyChunk { @@ -14960,7 +15073,7 @@ type ApproveDraftChunkRequest struct { func (x *ApproveDraftChunkRequest) Reset() { *x = ApproveDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[191] + mi := &file_openshell_proto_msgTypes[193] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14972,7 +15085,7 @@ func (x *ApproveDraftChunkRequest) String() string { func (*ApproveDraftChunkRequest) ProtoMessage() {} func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[191] + mi := &file_openshell_proto_msgTypes[193] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14985,7 +15098,7 @@ func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkRequest.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{191} + return file_openshell_proto_rawDescGZIP(), []int{193} } func (x *ApproveDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -15035,7 +15148,7 @@ type ApproveDraftChunkResponse struct { func (x *ApproveDraftChunkResponse) Reset() { *x = ApproveDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[192] + mi := &file_openshell_proto_msgTypes[194] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15047,7 +15160,7 @@ func (x *ApproveDraftChunkResponse) String() string { func (*ApproveDraftChunkResponse) ProtoMessage() {} func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[192] + mi := &file_openshell_proto_msgTypes[194] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15060,7 +15173,7 @@ func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkResponse.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{192} + return file_openshell_proto_rawDescGZIP(), []int{194} } func (x *ApproveDraftChunkResponse) GetPolicyVersion() uint32 { @@ -15096,7 +15209,7 @@ type RejectDraftChunkRequest struct { func (x *RejectDraftChunkRequest) Reset() { *x = RejectDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[193] + mi := &file_openshell_proto_msgTypes[195] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15108,7 +15221,7 @@ func (x *RejectDraftChunkRequest) String() string { func (*RejectDraftChunkRequest) ProtoMessage() {} func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[193] + mi := &file_openshell_proto_msgTypes[195] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15121,7 +15234,7 @@ func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkRequest.ProtoReflect.Descriptor instead. func (*RejectDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{193} + return file_openshell_proto_rawDescGZIP(), []int{195} } func (x *RejectDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -15167,7 +15280,7 @@ type RejectDraftChunkResponse struct { func (x *RejectDraftChunkResponse) Reset() { *x = RejectDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[194] + mi := &file_openshell_proto_msgTypes[196] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15179,7 +15292,7 @@ func (x *RejectDraftChunkResponse) String() string { func (*RejectDraftChunkResponse) ProtoMessage() {} func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[194] + mi := &file_openshell_proto_msgTypes[196] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15192,7 +15305,7 @@ func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkResponse.ProtoReflect.Descriptor instead. func (*RejectDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{194} + return file_openshell_proto_rawDescGZIP(), []int{196} } // Approve all pending chunks. @@ -15206,7 +15319,7 @@ type DraftChunkApproval struct { func (x *DraftChunkApproval) Reset() { *x = DraftChunkApproval{} - mi := &file_openshell_proto_msgTypes[195] + mi := &file_openshell_proto_msgTypes[197] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15218,7 +15331,7 @@ func (x *DraftChunkApproval) String() string { func (*DraftChunkApproval) ProtoMessage() {} func (x *DraftChunkApproval) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[195] + mi := &file_openshell_proto_msgTypes[197] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15231,7 +15344,7 @@ func (x *DraftChunkApproval) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftChunkApproval.ProtoReflect.Descriptor instead. func (*DraftChunkApproval) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{195} + return file_openshell_proto_rawDescGZIP(), []int{197} } func (x *DraftChunkApproval) GetChunkId() string { @@ -15267,7 +15380,7 @@ type ApproveAllDraftChunksRequest struct { func (x *ApproveAllDraftChunksRequest) Reset() { *x = ApproveAllDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[196] + mi := &file_openshell_proto_msgTypes[198] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15279,7 +15392,7 @@ func (x *ApproveAllDraftChunksRequest) String() string { func (*ApproveAllDraftChunksRequest) ProtoMessage() {} func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[196] + mi := &file_openshell_proto_msgTypes[198] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15292,7 +15405,7 @@ func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{196} + return file_openshell_proto_rawDescGZIP(), []int{198} } func (x *ApproveAllDraftChunksRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -15347,7 +15460,7 @@ type ApproveAllDraftChunksResponse struct { func (x *ApproveAllDraftChunksResponse) Reset() { *x = ApproveAllDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[197] + mi := &file_openshell_proto_msgTypes[199] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15359,7 +15472,7 @@ func (x *ApproveAllDraftChunksResponse) String() string { func (*ApproveAllDraftChunksResponse) ProtoMessage() {} func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[197] + mi := &file_openshell_proto_msgTypes[199] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15372,7 +15485,7 @@ func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{197} + return file_openshell_proto_rawDescGZIP(), []int{199} } func (x *ApproveAllDraftChunksResponse) GetPolicyVersion() uint32 { @@ -15422,7 +15535,7 @@ type EditDraftChunkRequest struct { func (x *EditDraftChunkRequest) Reset() { *x = EditDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[198] + mi := &file_openshell_proto_msgTypes[200] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15434,7 +15547,7 @@ func (x *EditDraftChunkRequest) String() string { func (*EditDraftChunkRequest) ProtoMessage() {} func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[198] + mi := &file_openshell_proto_msgTypes[200] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15447,7 +15560,7 @@ func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkRequest.ProtoReflect.Descriptor instead. func (*EditDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{198} + return file_openshell_proto_rawDescGZIP(), []int{200} } func (x *EditDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -15493,7 +15606,7 @@ type EditDraftChunkResponse struct { func (x *EditDraftChunkResponse) Reset() { *x = EditDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[199] + mi := &file_openshell_proto_msgTypes[201] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15505,7 +15618,7 @@ func (x *EditDraftChunkResponse) String() string { func (*EditDraftChunkResponse) ProtoMessage() {} func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[199] + mi := &file_openshell_proto_msgTypes[201] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15518,7 +15631,7 @@ func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkResponse.ProtoReflect.Descriptor instead. func (*EditDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{199} + return file_openshell_proto_rawDescGZIP(), []int{201} } // Reverse an approval (remove merged rule from active policy). @@ -15538,7 +15651,7 @@ type UndoDraftChunkRequest struct { func (x *UndoDraftChunkRequest) Reset() { *x = UndoDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[200] + mi := &file_openshell_proto_msgTypes[202] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15550,7 +15663,7 @@ func (x *UndoDraftChunkRequest) String() string { func (*UndoDraftChunkRequest) ProtoMessage() {} func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[200] + mi := &file_openshell_proto_msgTypes[202] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15563,7 +15676,7 @@ func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkRequest.ProtoReflect.Descriptor instead. func (*UndoDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{200} + return file_openshell_proto_rawDescGZIP(), []int{202} } func (x *UndoDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -15606,7 +15719,7 @@ type UndoDraftChunkResponse struct { func (x *UndoDraftChunkResponse) Reset() { *x = UndoDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[201] + mi := &file_openshell_proto_msgTypes[203] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15618,7 +15731,7 @@ func (x *UndoDraftChunkResponse) String() string { func (*UndoDraftChunkResponse) ProtoMessage() {} func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[201] + mi := &file_openshell_proto_msgTypes[203] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15631,7 +15744,7 @@ func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkResponse.ProtoReflect.Descriptor instead. func (*UndoDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{201} + return file_openshell_proto_rawDescGZIP(), []int{203} } func (x *UndoDraftChunkResponse) GetPolicyVersion() uint32 { @@ -15663,7 +15776,7 @@ type ClearDraftChunksRequest struct { func (x *ClearDraftChunksRequest) Reset() { *x = ClearDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[202] + mi := &file_openshell_proto_msgTypes[204] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15675,7 +15788,7 @@ func (x *ClearDraftChunksRequest) String() string { func (*ClearDraftChunksRequest) ProtoMessage() {} func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[202] + mi := &file_openshell_proto_msgTypes[204] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15688,7 +15801,7 @@ func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ClearDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{202} + return file_openshell_proto_rawDescGZIP(), []int{204} } func (x *ClearDraftChunksRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -15722,7 +15835,7 @@ type ClearDraftChunksResponse struct { func (x *ClearDraftChunksResponse) Reset() { *x = ClearDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[203] + mi := &file_openshell_proto_msgTypes[205] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15734,7 +15847,7 @@ func (x *ClearDraftChunksResponse) String() string { func (*ClearDraftChunksResponse) ProtoMessage() {} func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[203] + mi := &file_openshell_proto_msgTypes[205] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15747,7 +15860,7 @@ func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ClearDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{203} + return file_openshell_proto_rawDescGZIP(), []int{205} } func (x *ClearDraftChunksResponse) GetChunksCleared() uint32 { @@ -15769,7 +15882,7 @@ type GetDraftHistoryRequest struct { func (x *GetDraftHistoryRequest) Reset() { *x = GetDraftHistoryRequest{} - mi := &file_openshell_proto_msgTypes[204] + mi := &file_openshell_proto_msgTypes[206] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15781,7 +15894,7 @@ func (x *GetDraftHistoryRequest) String() string { func (*GetDraftHistoryRequest) ProtoMessage() {} func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[204] + mi := &file_openshell_proto_msgTypes[206] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15794,7 +15907,7 @@ func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryRequest.ProtoReflect.Descriptor instead. func (*GetDraftHistoryRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{204} + return file_openshell_proto_rawDescGZIP(), []int{206} } func (x *GetDraftHistoryRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -15828,7 +15941,7 @@ type DraftHistoryEntry struct { func (x *DraftHistoryEntry) Reset() { *x = DraftHistoryEntry{} - mi := &file_openshell_proto_msgTypes[205] + mi := &file_openshell_proto_msgTypes[207] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15840,7 +15953,7 @@ func (x *DraftHistoryEntry) String() string { func (*DraftHistoryEntry) ProtoMessage() {} func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[205] + mi := &file_openshell_proto_msgTypes[207] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15853,7 +15966,7 @@ func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftHistoryEntry.ProtoReflect.Descriptor instead. func (*DraftHistoryEntry) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{205} + return file_openshell_proto_rawDescGZIP(), []int{207} } func (x *DraftHistoryEntry) GetEventTime() *timestamppb.Timestamp { @@ -15894,7 +16007,7 @@ type GetDraftHistoryResponse struct { func (x *GetDraftHistoryResponse) Reset() { *x = GetDraftHistoryResponse{} - mi := &file_openshell_proto_msgTypes[206] + mi := &file_openshell_proto_msgTypes[208] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15906,7 +16019,7 @@ func (x *GetDraftHistoryResponse) String() string { func (*GetDraftHistoryResponse) ProtoMessage() {} func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[206] + mi := &file_openshell_proto_msgTypes[208] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15919,7 +16032,7 @@ func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryResponse.ProtoReflect.Descriptor instead. func (*GetDraftHistoryResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{206} + return file_openshell_proto_rawDescGZIP(), []int{208} } func (x *GetDraftHistoryResponse) GetEntries() []*DraftHistoryEntry { @@ -15944,7 +16057,7 @@ type CreateWorkspaceRequest struct { func (x *CreateWorkspaceRequest) Reset() { *x = CreateWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[207] + mi := &file_openshell_proto_msgTypes[209] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15956,7 +16069,7 @@ func (x *CreateWorkspaceRequest) String() string { func (*CreateWorkspaceRequest) ProtoMessage() {} func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[207] + mi := &file_openshell_proto_msgTypes[209] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15969,7 +16082,7 @@ func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceRequest.ProtoReflect.Descriptor instead. func (*CreateWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{207} + return file_openshell_proto_rawDescGZIP(), []int{209} } func (x *CreateWorkspaceRequest) GetName() string { @@ -16003,7 +16116,7 @@ type CreateWorkspaceResponse struct { func (x *CreateWorkspaceResponse) Reset() { *x = CreateWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[208] + mi := &file_openshell_proto_msgTypes[210] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16015,7 +16128,7 @@ func (x *CreateWorkspaceResponse) String() string { func (*CreateWorkspaceResponse) ProtoMessage() {} func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[208] + mi := &file_openshell_proto_msgTypes[210] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16028,7 +16141,7 @@ func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceResponse.ProtoReflect.Descriptor instead. func (*CreateWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{208} + return file_openshell_proto_rawDescGZIP(), []int{210} } func (x *CreateWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -16049,7 +16162,7 @@ type GetWorkspaceRequest struct { func (x *GetWorkspaceRequest) Reset() { *x = GetWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[209] + mi := &file_openshell_proto_msgTypes[211] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16061,7 +16174,7 @@ func (x *GetWorkspaceRequest) String() string { func (*GetWorkspaceRequest) ProtoMessage() {} func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[209] + mi := &file_openshell_proto_msgTypes[211] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16074,7 +16187,7 @@ func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceRequest.ProtoReflect.Descriptor instead. func (*GetWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{209} + return file_openshell_proto_rawDescGZIP(), []int{211} } func (x *GetWorkspaceRequest) GetName() string { @@ -16094,7 +16207,7 @@ type GetWorkspaceResponse struct { func (x *GetWorkspaceResponse) Reset() { *x = GetWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[210] + mi := &file_openshell_proto_msgTypes[212] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16106,7 +16219,7 @@ func (x *GetWorkspaceResponse) String() string { func (*GetWorkspaceResponse) ProtoMessage() {} func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[210] + mi := &file_openshell_proto_msgTypes[212] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16119,7 +16232,7 @@ func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceResponse.ProtoReflect.Descriptor instead. func (*GetWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{210} + return file_openshell_proto_rawDescGZIP(), []int{212} } func (x *GetWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -16146,7 +16259,7 @@ type ListWorkspacesRequest struct { func (x *ListWorkspacesRequest) Reset() { *x = ListWorkspacesRequest{} - mi := &file_openshell_proto_msgTypes[211] + mi := &file_openshell_proto_msgTypes[213] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16158,7 +16271,7 @@ func (x *ListWorkspacesRequest) String() string { func (*ListWorkspacesRequest) ProtoMessage() {} func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[211] + mi := &file_openshell_proto_msgTypes[213] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16171,7 +16284,7 @@ func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesRequest.ProtoReflect.Descriptor instead. func (*ListWorkspacesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{211} + return file_openshell_proto_rawDescGZIP(), []int{213} } func (x *ListWorkspacesRequest) GetPageSize() int32 { @@ -16207,7 +16320,7 @@ type ListWorkspacesResponse struct { func (x *ListWorkspacesResponse) Reset() { *x = ListWorkspacesResponse{} - mi := &file_openshell_proto_msgTypes[212] + mi := &file_openshell_proto_msgTypes[214] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16219,7 +16332,7 @@ func (x *ListWorkspacesResponse) String() string { func (*ListWorkspacesResponse) ProtoMessage() {} func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[212] + mi := &file_openshell_proto_msgTypes[214] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16232,7 +16345,7 @@ func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesResponse.ProtoReflect.Descriptor instead. func (*ListWorkspacesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{212} + return file_openshell_proto_rawDescGZIP(), []int{214} } func (x *ListWorkspacesResponse) GetWorkspaces() []*datamodelv1.Workspace { @@ -16263,7 +16376,7 @@ type DeleteWorkspaceRequest struct { func (x *DeleteWorkspaceRequest) Reset() { *x = DeleteWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[213] + mi := &file_openshell_proto_msgTypes[215] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16275,7 +16388,7 @@ func (x *DeleteWorkspaceRequest) String() string { func (*DeleteWorkspaceRequest) ProtoMessage() {} func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[213] + mi := &file_openshell_proto_msgTypes[215] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16288,7 +16401,7 @@ func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceRequest.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{213} + return file_openshell_proto_rawDescGZIP(), []int{215} } func (x *DeleteWorkspaceRequest) GetName() string { @@ -16322,7 +16435,7 @@ type DeleteWorkspaceResponse struct { func (x *DeleteWorkspaceResponse) Reset() { *x = DeleteWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[214] + mi := &file_openshell_proto_msgTypes[216] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16334,7 +16447,7 @@ func (x *DeleteWorkspaceResponse) String() string { func (*DeleteWorkspaceResponse) ProtoMessage() {} func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[214] + mi := &file_openshell_proto_msgTypes[216] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16347,7 +16460,7 @@ func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceResponse.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{214} + return file_openshell_proto_rawDescGZIP(), []int{216} } func (x *DeleteWorkspaceResponse) GetOutcome() DeletionOutcome { @@ -16371,7 +16484,7 @@ type WorkspaceMember struct { func (x *WorkspaceMember) Reset() { *x = WorkspaceMember{} - mi := &file_openshell_proto_msgTypes[215] + mi := &file_openshell_proto_msgTypes[217] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16383,7 +16496,7 @@ func (x *WorkspaceMember) String() string { func (*WorkspaceMember) ProtoMessage() {} func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[215] + mi := &file_openshell_proto_msgTypes[217] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16396,7 +16509,7 @@ func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspaceMember.ProtoReflect.Descriptor instead. func (*WorkspaceMember) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{215} + return file_openshell_proto_rawDescGZIP(), []int{217} } func (x *WorkspaceMember) GetMetadata() *datamodelv1.ObjectMeta { @@ -16437,7 +16550,7 @@ type AddWorkspaceMemberRequest struct { func (x *AddWorkspaceMemberRequest) Reset() { *x = AddWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[216] + mi := &file_openshell_proto_msgTypes[218] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16449,7 +16562,7 @@ func (x *AddWorkspaceMemberRequest) String() string { func (*AddWorkspaceMemberRequest) ProtoMessage() {} func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[216] + mi := &file_openshell_proto_msgTypes[218] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16462,7 +16575,7 @@ func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{216} + return file_openshell_proto_rawDescGZIP(), []int{218} } func (x *AddWorkspaceMemberRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16503,7 +16616,7 @@ type AddWorkspaceMemberResponse struct { func (x *AddWorkspaceMemberResponse) Reset() { *x = AddWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[217] + mi := &file_openshell_proto_msgTypes[219] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16515,7 +16628,7 @@ func (x *AddWorkspaceMemberResponse) String() string { func (*AddWorkspaceMemberResponse) ProtoMessage() {} func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[217] + mi := &file_openshell_proto_msgTypes[219] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16528,7 +16641,7 @@ func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{217} + return file_openshell_proto_rawDescGZIP(), []int{219} } func (x *AddWorkspaceMemberResponse) GetMember() *WorkspaceMember { @@ -16554,7 +16667,7 @@ type RemoveWorkspaceMemberRequest struct { func (x *RemoveWorkspaceMemberRequest) Reset() { *x = RemoveWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[218] + mi := &file_openshell_proto_msgTypes[220] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16566,7 +16679,7 @@ func (x *RemoveWorkspaceMemberRequest) String() string { func (*RemoveWorkspaceMemberRequest) ProtoMessage() {} func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[218] + mi := &file_openshell_proto_msgTypes[220] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16579,7 +16692,7 @@ func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{218} + return file_openshell_proto_rawDescGZIP(), []int{220} } func (x *RemoveWorkspaceMemberRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16620,7 +16733,7 @@ type RemoveWorkspaceMemberResponse struct { func (x *RemoveWorkspaceMemberResponse) Reset() { *x = RemoveWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[219] + mi := &file_openshell_proto_msgTypes[221] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16632,7 +16745,7 @@ func (x *RemoveWorkspaceMemberResponse) String() string { func (*RemoveWorkspaceMemberResponse) ProtoMessage() {} func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[219] + mi := &file_openshell_proto_msgTypes[221] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16645,7 +16758,7 @@ func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{219} + return file_openshell_proto_rawDescGZIP(), []int{221} } func (x *RemoveWorkspaceMemberResponse) GetOutcome() DeletionOutcome { @@ -16672,7 +16785,7 @@ type ListWorkspaceMembersRequest struct { func (x *ListWorkspaceMembersRequest) Reset() { *x = ListWorkspaceMembersRequest{} - mi := &file_openshell_proto_msgTypes[220] + mi := &file_openshell_proto_msgTypes[222] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16684,7 +16797,7 @@ func (x *ListWorkspaceMembersRequest) String() string { func (*ListWorkspaceMembersRequest) ProtoMessage() {} func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[220] + mi := &file_openshell_proto_msgTypes[222] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16697,7 +16810,7 @@ func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersRequest.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{220} + return file_openshell_proto_rawDescGZIP(), []int{222} } func (x *ListWorkspaceMembersRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16733,7 +16846,7 @@ type ListWorkspaceMembersResponse struct { func (x *ListWorkspaceMembersResponse) Reset() { *x = ListWorkspaceMembersResponse{} - mi := &file_openshell_proto_msgTypes[221] + mi := &file_openshell_proto_msgTypes[223] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16745,7 +16858,7 @@ func (x *ListWorkspaceMembersResponse) String() string { func (*ListWorkspaceMembersResponse) ProtoMessage() {} func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[221] + mi := &file_openshell_proto_msgTypes[223] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16758,7 +16871,7 @@ func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersResponse.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{221} + return file_openshell_proto_rawDescGZIP(), []int{223} } func (x *ListWorkspaceMembersResponse) GetMembers() []*WorkspaceMember { @@ -16793,7 +16906,7 @@ type ExtensionServiceCredential struct { func (x *ExtensionServiceCredential) Reset() { *x = ExtensionServiceCredential{} - mi := &file_openshell_proto_msgTypes[222] + mi := &file_openshell_proto_msgTypes[224] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16805,7 +16918,7 @@ func (x *ExtensionServiceCredential) String() string { func (*ExtensionServiceCredential) ProtoMessage() {} func (x *ExtensionServiceCredential) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[222] + mi := &file_openshell_proto_msgTypes[224] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16818,7 +16931,7 @@ func (x *ExtensionServiceCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use ExtensionServiceCredential.ProtoReflect.Descriptor instead. func (*ExtensionServiceCredential) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{222} + return file_openshell_proto_rawDescGZIP(), []int{224} } func (x *ExtensionServiceCredential) GetServiceName() string { @@ -16855,7 +16968,7 @@ type EndpointObservation struct { func (x *EndpointObservation) Reset() { *x = EndpointObservation{} - mi := &file_openshell_proto_msgTypes[223] + mi := &file_openshell_proto_msgTypes[225] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16867,7 +16980,7 @@ func (x *EndpointObservation) String() string { func (*EndpointObservation) ProtoMessage() {} func (x *EndpointObservation) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[223] + mi := &file_openshell_proto_msgTypes[225] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16880,7 +16993,7 @@ func (x *EndpointObservation) ProtoReflect() protoreflect.Message { // Deprecated: Use EndpointObservation.ProtoReflect.Descriptor instead. func (*EndpointObservation) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{223} + return file_openshell_proto_rawDescGZIP(), []int{225} } func (x *EndpointObservation) GetEndpointId() string { @@ -16923,7 +17036,7 @@ type ReportEndpointStatusRequest struct { func (x *ReportEndpointStatusRequest) Reset() { *x = ReportEndpointStatusRequest{} - mi := &file_openshell_proto_msgTypes[224] + mi := &file_openshell_proto_msgTypes[226] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16935,7 +17048,7 @@ func (x *ReportEndpointStatusRequest) String() string { func (*ReportEndpointStatusRequest) ProtoMessage() {} func (x *ReportEndpointStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[224] + mi := &file_openshell_proto_msgTypes[226] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16948,7 +17061,7 @@ func (x *ReportEndpointStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportEndpointStatusRequest.ProtoReflect.Descriptor instead. func (*ReportEndpointStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{224} + return file_openshell_proto_rawDescGZIP(), []int{226} } func (x *ReportEndpointStatusRequest) GetSandboxId() string { @@ -17009,7 +17122,7 @@ type ReportEndpointStatusResponse struct { func (x *ReportEndpointStatusResponse) Reset() { *x = ReportEndpointStatusResponse{} - mi := &file_openshell_proto_msgTypes[225] + mi := &file_openshell_proto_msgTypes[227] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17021,7 +17134,7 @@ func (x *ReportEndpointStatusResponse) String() string { func (*ReportEndpointStatusResponse) ProtoMessage() {} func (x *ReportEndpointStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[225] + mi := &file_openshell_proto_msgTypes[227] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17034,7 +17147,7 @@ func (x *ReportEndpointStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportEndpointStatusResponse.ProtoReflect.Descriptor instead. func (*ReportEndpointStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{225} + return file_openshell_proto_rawDescGZIP(), []int{227} } // A configured endpoint and its last accepted network result in one record. @@ -17063,7 +17176,7 @@ type EndpointStatus struct { func (x *EndpointStatus) Reset() { *x = EndpointStatus{} - mi := &file_openshell_proto_msgTypes[226] + mi := &file_openshell_proto_msgTypes[228] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17075,7 +17188,7 @@ func (x *EndpointStatus) String() string { func (*EndpointStatus) ProtoMessage() {} func (x *EndpointStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[226] + mi := &file_openshell_proto_msgTypes[228] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17088,7 +17201,7 @@ func (x *EndpointStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use EndpointStatus.ProtoReflect.Descriptor instead. func (*EndpointStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{226} + return file_openshell_proto_rawDescGZIP(), []int{228} } func (x *EndpointStatus) GetEndpointId() string { @@ -17158,7 +17271,7 @@ type SandboxProvisioning struct { func (x *SandboxProvisioning) Reset() { *x = SandboxProvisioning{} - mi := &file_openshell_proto_msgTypes[227] + mi := &file_openshell_proto_msgTypes[229] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17170,7 +17283,7 @@ func (x *SandboxProvisioning) String() string { func (*SandboxProvisioning) ProtoMessage() {} func (x *SandboxProvisioning) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[227] + mi := &file_openshell_proto_msgTypes[229] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17183,7 +17296,7 @@ func (x *SandboxProvisioning) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxProvisioning.ProtoReflect.Descriptor instead. func (*SandboxProvisioning) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{227} + return file_openshell_proto_rawDescGZIP(), []int{229} } func (x *SandboxProvisioning) GetAttemptId() string { @@ -17276,7 +17389,7 @@ type SandboxServiceExposure struct { func (x *SandboxServiceExposure) Reset() { *x = SandboxServiceExposure{} - mi := &file_openshell_proto_msgTypes[228] + mi := &file_openshell_proto_msgTypes[230] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17288,7 +17401,7 @@ func (x *SandboxServiceExposure) String() string { func (*SandboxServiceExposure) ProtoMessage() {} func (x *SandboxServiceExposure) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[228] + mi := &file_openshell_proto_msgTypes[230] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17301,7 +17414,7 @@ func (x *SandboxServiceExposure) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxServiceExposure.ProtoReflect.Descriptor instead. func (*SandboxServiceExposure) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{228} + return file_openshell_proto_rawDescGZIP(), []int{230} } func (x *SandboxServiceExposure) GetService() string { @@ -17322,7 +17435,16 @@ var File_openshell_proto protoreflect.FileDescriptor const file_openshell_proto_rawDesc = "" + "\n" + - "\x0fopenshell.proto\x12\fopenshell.v1\x1a\x0fdatamodel.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\roptions.proto\x1a\rsandbox.proto\"\x1a\n" + + "\x0fopenshell.proto\x12\fopenshell.v1\x1a\x0fdatamodel.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\roptions.proto\x1a\rsandbox.proto\"\x1b\n" + + "\x19RegisterSupervisorRequest\"\xe0\x01\n" + + "\x1aRegisterSupervisorResponse\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12!\n" + + "\fsandbox_name\x18\x02 \x01(\tR\vsandboxName\x12-\n" + + "\x12backend_descriptor\x18\x03 \x01(\fR\x11backendDescriptor\x12%\n" + + "\vauth_bundle\x18\x04 \x01(\fB\x04\x88\xb5\x18\x01R\n" + + "authBundle\x12*\n" + + "\x11main_process_spec\x18\x05 \x01(\tR\x0fmainProcessSpec\"\x1a\n" + "\x18IssueSandboxTokenRequest\"\x91\x01\n" + "\x19IssueSandboxTokenResponse\x12\x1a\n" + "\x05token\x18\x01 \x01(\tB\x04\x88\xb5\x18\x01R\x05token\x12C\n" + @@ -18751,7 +18873,7 @@ const file_openshell_proto_rawDesc = "" + "&ENDPOINT_RESULT_CREDENTIAL_UNAVAILABLE\x10\x04\x12\x1e\n" + "\x1aENDPOINT_RESULT_TLS_FAILED\x10\x05\x12$\n" + " ENDPOINT_RESULT_TRANSPORT_FAILED\x10\x06\x12%\n" + - "!ENDPOINT_RESULT_UPSTREAM_REJECTED\x10\a2\xc2Q\n" + + "!ENDPOINT_RESULT_UPSTREAM_REJECTED\x10\a2\xbaR\n" + "\tOpenShell\x12Z\n" + "\x06Health\x12\x1b.openshell.v1.HealthRequest\x1a\x1c.openshell.v1.HealthResponse\"\x15\x82\xb5\x18\x11\n" + "\x0funauthenticated\x12i\n" + @@ -18893,7 +19015,9 @@ const file_openshell_proto_rawDesc = "" + "\x10ClearDraftChunks\x12%.openshell.v1.ClearDraftChunksRequest\x1a&.openshell.v1.ClearDraftChunksResponse\"!\x82\xb5\x18\x1d\n" + "\x06bearer\x12\x05admin\"\fconfig:write\x12\x7f\n" + "\x0fGetDraftHistory\x12$.openshell.v1.GetDraftHistoryRequest\x1a%.openshell.v1.GetDraftHistoryResponse\"\x1f\x82\xb5\x18\x1b\n" + - "\x06bearer\x12\x04user\"\vconfig:read\x12s\n" + + "\x06bearer\x12\x04user\"\vconfig:read\x12v\n" + + "\x12RegisterSupervisor\x12'.openshell.v1.RegisterSupervisorRequest\x1a(.openshell.v1.RegisterSupervisorResponse\"\r\x82\xb5\x18\t\n" + + "\asandbox\x12s\n" + "\x11IssueSandboxToken\x12&.openshell.v1.IssueSandboxTokenRequest\x1a'.openshell.v1.IssueSandboxTokenResponse\"\r\x82\xb5\x18\t\n" + "\asandbox\x12y\n" + "\x13RefreshSandboxToken\x12(.openshell.v1.RefreshSandboxTokenRequest\x1a).openshell.v1.RefreshSandboxTokenResponse\"\r\x82\xb5\x18\t\n" + @@ -18926,7 +19050,7 @@ func file_openshell_proto_rawDescGZIP() []byte { } var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 18) -var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 251) +var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 253) var file_openshell_proto_goTypes = []any{ (ExtensionKind)(0), // 0: openshell.v1.ExtensionKind (SandboxPhase)(0), // 1: openshell.v1.SandboxPhase @@ -18946,750 +19070,754 @@ var file_openshell_proto_goTypes = []any{ (ProviderCredentialRefreshRecoveryAction)(0), // 15: openshell.v1.ProviderCredentialRefreshRecoveryAction (DeletionOutcome)(0), // 16: openshell.v1.DeletionOutcome (EndpointResult)(0), // 17: openshell.v1.EndpointResult - (*IssueSandboxTokenRequest)(nil), // 18: openshell.v1.IssueSandboxTokenRequest - (*IssueSandboxTokenResponse)(nil), // 19: openshell.v1.IssueSandboxTokenResponse - (*RefreshSandboxTokenRequest)(nil), // 20: openshell.v1.RefreshSandboxTokenRequest - (*RefreshSandboxTokenResponse)(nil), // 21: openshell.v1.RefreshSandboxTokenResponse - (*HealthRequest)(nil), // 22: openshell.v1.HealthRequest - (*HealthResponse)(nil), // 23: openshell.v1.HealthResponse - (*GetCurrentUserRequest)(nil), // 24: openshell.v1.GetCurrentUserRequest - (*GetCurrentUserResponse)(nil), // 25: openshell.v1.GetCurrentUserResponse - (*GetGatewayInfoRequest)(nil), // 26: openshell.v1.GetGatewayInfoRequest - (*GetGatewayInfoResponse)(nil), // 27: openshell.v1.GetGatewayInfoResponse - (*NegotiatedExtensionInfo)(nil), // 28: openshell.v1.NegotiatedExtensionInfo - (*ComputeDriverInfo)(nil), // 29: openshell.v1.ComputeDriverInfo - (*ComputeDriverCapabilities)(nil), // 30: openshell.v1.ComputeDriverCapabilities - (*ResourceCapabilities)(nil), // 31: openshell.v1.ResourceCapabilities - (*CpuResourceCapabilities)(nil), // 32: openshell.v1.CpuResourceCapabilities - (*MemoryResourceCapabilities)(nil), // 33: openshell.v1.MemoryResourceCapabilities - (*GpuResourceCapabilities)(nil), // 34: openshell.v1.GpuResourceCapabilities - (*Sandbox)(nil), // 35: openshell.v1.Sandbox - (*SandboxSpec)(nil), // 36: openshell.v1.SandboxSpec - (*ResourceRequirements)(nil), // 37: openshell.v1.ResourceRequirements - (*GpuResourceRequirements)(nil), // 38: openshell.v1.GpuResourceRequirements - (*SandboxTemplate)(nil), // 39: openshell.v1.SandboxTemplate - (*SandboxWorkloadTemplate)(nil), // 40: openshell.v1.SandboxWorkloadTemplate - (*SandboxWorkloadTemplateSpec)(nil), // 41: openshell.v1.SandboxWorkloadTemplateSpec - (*SandboxWorkloadConfig)(nil), // 42: openshell.v1.SandboxWorkloadConfig - (*SandboxResources)(nil), // 43: openshell.v1.SandboxResources - (*SandboxServiceLevel)(nil), // 44: openshell.v1.SandboxServiceLevel - (*SandboxStartup)(nil), // 45: openshell.v1.SandboxStartup - (*SandboxWorkloadTemplateProvenance)(nil), // 46: openshell.v1.SandboxWorkloadTemplateProvenance - (*SandboxStatus)(nil), // 47: openshell.v1.SandboxStatus - (*SandboxCondition)(nil), // 48: openshell.v1.SandboxCondition - (*PlatformEvent)(nil), // 49: openshell.v1.PlatformEvent - (*CreateSandboxRequest)(nil), // 50: openshell.v1.CreateSandboxRequest - (*CreateSandboxTemplateRequest)(nil), // 51: openshell.v1.CreateSandboxTemplateRequest - (*GetSandboxTemplateRequest)(nil), // 52: openshell.v1.GetSandboxTemplateRequest - (*ListSandboxTemplatesRequest)(nil), // 53: openshell.v1.ListSandboxTemplatesRequest - (*DeleteSandboxTemplateRequest)(nil), // 54: openshell.v1.DeleteSandboxTemplateRequest - (*SandboxTemplateResponse)(nil), // 55: openshell.v1.SandboxTemplateResponse - (*ListSandboxTemplatesResponse)(nil), // 56: openshell.v1.ListSandboxTemplatesResponse - (*DeleteSandboxTemplateResponse)(nil), // 57: openshell.v1.DeleteSandboxTemplateResponse - (*BeginRootfsTarStagingRequest)(nil), // 58: openshell.v1.BeginRootfsTarStagingRequest - (*BeginRootfsTarStagingResponse)(nil), // 59: openshell.v1.BeginRootfsTarStagingResponse - (*GetSandboxRequest)(nil), // 60: openshell.v1.GetSandboxRequest - (*ListSandboxesRequest)(nil), // 61: openshell.v1.ListSandboxesRequest - (*ListSandboxProvidersRequest)(nil), // 62: openshell.v1.ListSandboxProvidersRequest - (*AttachSandboxProviderRequest)(nil), // 63: openshell.v1.AttachSandboxProviderRequest - (*DetachSandboxProviderRequest)(nil), // 64: openshell.v1.DetachSandboxProviderRequest - (*DeleteSandboxRequest)(nil), // 65: openshell.v1.DeleteSandboxRequest - (*StopSandboxRequest)(nil), // 66: openshell.v1.StopSandboxRequest - (*StartSandboxRequest)(nil), // 67: openshell.v1.StartSandboxRequest - (*SandboxResponse)(nil), // 68: openshell.v1.SandboxResponse - (*ListSandboxesResponse)(nil), // 69: openshell.v1.ListSandboxesResponse - (*ListSandboxProvidersResponse)(nil), // 70: openshell.v1.ListSandboxProvidersResponse - (*AttachSandboxProviderResponse)(nil), // 71: openshell.v1.AttachSandboxProviderResponse - (*DetachSandboxProviderResponse)(nil), // 72: openshell.v1.DetachSandboxProviderResponse - (*ProviderDesiredIdentity)(nil), // 73: openshell.v1.ProviderDesiredIdentity - (*ConfigSnapshotRevision)(nil), // 74: openshell.v1.ConfigSnapshotRevision - (*SandboxConfigRevision)(nil), // 75: openshell.v1.SandboxConfigRevision - (*ConfigUpdateOperation)(nil), // 76: openshell.v1.ConfigUpdateOperation - (*ProviderMutationReceipt)(nil), // 77: openshell.v1.ProviderMutationReceipt - (*ProviderReadinessObservation)(nil), // 78: openshell.v1.ProviderReadinessObservation - (*ProviderReadinessStatus)(nil), // 79: openshell.v1.ProviderReadinessStatus - (*GetSandboxProviderStatusRequest)(nil), // 80: openshell.v1.GetSandboxProviderStatusRequest - (*GetSandboxProviderStatusResponse)(nil), // 81: openshell.v1.GetSandboxProviderStatusResponse - (*ReportProviderReadinessRequest)(nil), // 82: openshell.v1.ReportProviderReadinessRequest - (*ReportProviderReadinessResponse)(nil), // 83: openshell.v1.ReportProviderReadinessResponse - (*DeleteSandboxResponse)(nil), // 84: openshell.v1.DeleteSandboxResponse - (*CreateSshSessionRequest)(nil), // 85: openshell.v1.CreateSshSessionRequest - (*CreateSshSessionResponse)(nil), // 86: openshell.v1.CreateSshSessionResponse - (*ExposeServiceRequest)(nil), // 87: openshell.v1.ExposeServiceRequest - (*GetServiceRequest)(nil), // 88: openshell.v1.GetServiceRequest - (*ListServicesRequest)(nil), // 89: openshell.v1.ListServicesRequest - (*ListServicesResponse)(nil), // 90: openshell.v1.ListServicesResponse - (*DeleteServiceRequest)(nil), // 91: openshell.v1.DeleteServiceRequest - (*DeleteServiceResponse)(nil), // 92: openshell.v1.DeleteServiceResponse - (*ServiceEndpoint)(nil), // 93: openshell.v1.ServiceEndpoint - (*ServiceEndpointResponse)(nil), // 94: openshell.v1.ServiceEndpointResponse - (*RevokeSshSessionRequest)(nil), // 95: openshell.v1.RevokeSshSessionRequest - (*RevokeSshSessionResponse)(nil), // 96: openshell.v1.RevokeSshSessionResponse - (*ExecSandboxRequest)(nil), // 97: openshell.v1.ExecSandboxRequest - (*ExecSandboxStdout)(nil), // 98: openshell.v1.ExecSandboxStdout - (*ExecSandboxStderr)(nil), // 99: openshell.v1.ExecSandboxStderr - (*ExecSandboxExit)(nil), // 100: openshell.v1.ExecSandboxExit - (*ExecSandboxEvent)(nil), // 101: openshell.v1.ExecSandboxEvent - (*TcpForwardInit)(nil), // 102: openshell.v1.TcpForwardInit - (*TcpForwardFrame)(nil), // 103: openshell.v1.TcpForwardFrame - (*ExecSandboxInput)(nil), // 104: openshell.v1.ExecSandboxInput - (*ExecSandboxWindowResize)(nil), // 105: openshell.v1.ExecSandboxWindowResize - (*SshSession)(nil), // 106: openshell.v1.SshSession - (*WatchSandboxRequest)(nil), // 107: openshell.v1.WatchSandboxRequest - (*SandboxStreamEvent)(nil), // 108: openshell.v1.SandboxStreamEvent - (*SandboxLogLine)(nil), // 109: openshell.v1.SandboxLogLine - (*SandboxStreamWarning)(nil), // 110: openshell.v1.SandboxStreamWarning - (*CreateProviderRequest)(nil), // 111: openshell.v1.CreateProviderRequest - (*GetProviderRequest)(nil), // 112: openshell.v1.GetProviderRequest - (*ListProvidersRequest)(nil), // 113: openshell.v1.ListProvidersRequest - (*UpdateProviderRequest)(nil), // 114: openshell.v1.UpdateProviderRequest - (*DeleteProviderRequest)(nil), // 115: openshell.v1.DeleteProviderRequest - (*ProviderResponse)(nil), // 116: openshell.v1.ProviderResponse - (*ListProvidersResponse)(nil), // 117: openshell.v1.ListProvidersResponse - (*ListProviderProfilesRequest)(nil), // 118: openshell.v1.ListProviderProfilesRequest - (*GetProviderProfileRequest)(nil), // 119: openshell.v1.GetProviderProfileRequest - (*ProviderProfileImportItem)(nil), // 120: openshell.v1.ProviderProfileImportItem - (*ProviderProfileDiagnostic)(nil), // 121: openshell.v1.ProviderProfileDiagnostic - (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 122: openshell.v1.ProviderCredentialTokenGrantAudienceOverride - (*ProviderCredentialTokenGrantSubjectToken)(nil), // 123: openshell.v1.ProviderCredentialTokenGrantSubjectToken - (*ProviderCredentialTokenGrant)(nil), // 124: openshell.v1.ProviderCredentialTokenGrant - (*ProviderProfileCredential)(nil), // 125: openshell.v1.ProviderProfileCredential - (*ProviderCredentialRefreshMaterial)(nil), // 126: openshell.v1.ProviderCredentialRefreshMaterial - (*ProviderCredentialRefreshOutput)(nil), // 127: openshell.v1.ProviderCredentialRefreshOutput - (*ProviderCredentialRefresh)(nil), // 128: openshell.v1.ProviderCredentialRefresh - (*ProviderCredentialRefreshStatus)(nil), // 129: openshell.v1.ProviderCredentialRefreshStatus - (*ProviderProfileDiscovery)(nil), // 130: openshell.v1.ProviderProfileDiscovery - (*GetProviderRefreshStatusRequest)(nil), // 131: openshell.v1.GetProviderRefreshStatusRequest - (*GetProviderRefreshStatusResponse)(nil), // 132: openshell.v1.GetProviderRefreshStatusResponse - (*ConfigureProviderRefreshRequest)(nil), // 133: openshell.v1.ConfigureProviderRefreshRequest - (*ConfigureProviderRefreshResponse)(nil), // 134: openshell.v1.ConfigureProviderRefreshResponse - (*RotateProviderCredentialRequest)(nil), // 135: openshell.v1.RotateProviderCredentialRequest - (*RotateProviderCredentialResponse)(nil), // 136: openshell.v1.RotateProviderCredentialResponse - (*DeleteProviderRefreshRequest)(nil), // 137: openshell.v1.DeleteProviderRefreshRequest - (*DeleteProviderRefreshResponse)(nil), // 138: openshell.v1.DeleteProviderRefreshResponse - (*ProviderProfile)(nil), // 139: openshell.v1.ProviderProfile - (*ProviderProfileResponse)(nil), // 140: openshell.v1.ProviderProfileResponse - (*ListProviderProfilesResponse)(nil), // 141: openshell.v1.ListProviderProfilesResponse - (*ImportProviderProfilesRequest)(nil), // 142: openshell.v1.ImportProviderProfilesRequest - (*ImportProviderProfilesResponse)(nil), // 143: openshell.v1.ImportProviderProfilesResponse - (*UpdateProviderProfilesRequest)(nil), // 144: openshell.v1.UpdateProviderProfilesRequest - (*UpdateProviderProfilesResponse)(nil), // 145: openshell.v1.UpdateProviderProfilesResponse - (*LintProviderProfilesRequest)(nil), // 146: openshell.v1.LintProviderProfilesRequest - (*LintProviderProfilesResponse)(nil), // 147: openshell.v1.LintProviderProfilesResponse - (*DeleteProviderResponse)(nil), // 148: openshell.v1.DeleteProviderResponse - (*DeleteProviderProfileRequest)(nil), // 149: openshell.v1.DeleteProviderProfileRequest - (*DeleteProviderProfileResponse)(nil), // 150: openshell.v1.DeleteProviderProfileResponse - (*GetSandboxProviderEnvironmentRequest)(nil), // 151: openshell.v1.GetSandboxProviderEnvironmentRequest - (*StaticCredentialEndpointBinding)(nil), // 152: openshell.v1.StaticCredentialEndpointBinding - (*StaticCredentialBinding)(nil), // 153: openshell.v1.StaticCredentialBinding - (*GetSandboxProviderEnvironmentResponse)(nil), // 154: openshell.v1.GetSandboxProviderEnvironmentResponse - (*ExchangeProviderSubjectTokenRequest)(nil), // 155: openshell.v1.ExchangeProviderSubjectTokenRequest - (*ExchangeProviderSubjectTokenResponse)(nil), // 156: openshell.v1.ExchangeProviderSubjectTokenResponse - (*UpdateConfigRequest)(nil), // 157: openshell.v1.UpdateConfigRequest - (*PolicyMergeOperation)(nil), // 158: openshell.v1.PolicyMergeOperation - (*AddNetworkRule)(nil), // 159: openshell.v1.AddNetworkRule - (*RemoveNetworkEndpoint)(nil), // 160: openshell.v1.RemoveNetworkEndpoint - (*RemoveNetworkRule)(nil), // 161: openshell.v1.RemoveNetworkRule - (*L7RuleTarget)(nil), // 162: openshell.v1.L7RuleTarget - (*AddDenyRules)(nil), // 163: openshell.v1.AddDenyRules - (*AddAllowRules)(nil), // 164: openshell.v1.AddAllowRules - (*RemoveNetworkBinary)(nil), // 165: openshell.v1.RemoveNetworkBinary - (*UpdateConfigResponse)(nil), // 166: openshell.v1.UpdateConfigResponse - (*GetSandboxPolicyStatusRequest)(nil), // 167: openshell.v1.GetSandboxPolicyStatusRequest - (*GetSandboxPolicyStatusResponse)(nil), // 168: openshell.v1.GetSandboxPolicyStatusResponse - (*ListSandboxPoliciesRequest)(nil), // 169: openshell.v1.ListSandboxPoliciesRequest - (*ListSandboxPoliciesResponse)(nil), // 170: openshell.v1.ListSandboxPoliciesResponse - (*ReportPolicyStatusRequest)(nil), // 171: openshell.v1.ReportPolicyStatusRequest - (*ReportPolicyStatusResponse)(nil), // 172: openshell.v1.ReportPolicyStatusResponse - (*SandboxConfigurationAdmission)(nil), // 173: openshell.v1.SandboxConfigurationAdmission - (*ReportSandboxConfigurationRequest)(nil), // 174: openshell.v1.ReportSandboxConfigurationRequest - (*ReportSandboxConfigurationResponse)(nil), // 175: openshell.v1.ReportSandboxConfigurationResponse - (*SandboxPolicyRevision)(nil), // 176: openshell.v1.SandboxPolicyRevision - (*GetSandboxLogsRequest)(nil), // 177: openshell.v1.GetSandboxLogsRequest - (*PushSandboxLogsRequest)(nil), // 178: openshell.v1.PushSandboxLogsRequest - (*PushSandboxLogsResponse)(nil), // 179: openshell.v1.PushSandboxLogsResponse - (*GetSandboxLogsResponse)(nil), // 180: openshell.v1.GetSandboxLogsResponse - (*SupervisorMessage)(nil), // 181: openshell.v1.SupervisorMessage - (*GatewayMessage)(nil), // 182: openshell.v1.GatewayMessage - (*SupervisorHello)(nil), // 183: openshell.v1.SupervisorHello - (*SessionAccepted)(nil), // 184: openshell.v1.SessionAccepted - (*SessionRejected)(nil), // 185: openshell.v1.SessionRejected - (*SupervisorHeartbeat)(nil), // 186: openshell.v1.SupervisorHeartbeat - (*GatewayHeartbeat)(nil), // 187: openshell.v1.GatewayHeartbeat - (*ReportMainProcessExitRequest)(nil), // 188: openshell.v1.ReportMainProcessExitRequest - (*ReportMainProcessExitResponse)(nil), // 189: openshell.v1.ReportMainProcessExitResponse - (*FinalizeMainProcessExitRequest)(nil), // 190: openshell.v1.FinalizeMainProcessExitRequest - (*FinalizeMainProcessExitResponse)(nil), // 191: openshell.v1.FinalizeMainProcessExitResponse - (*RelayOpen)(nil), // 192: openshell.v1.RelayOpen - (*SshRelayTarget)(nil), // 193: openshell.v1.SshRelayTarget - (*TcpRelayTarget)(nil), // 194: openshell.v1.TcpRelayTarget - (*RelayInit)(nil), // 195: openshell.v1.RelayInit - (*RelayFrame)(nil), // 196: openshell.v1.RelayFrame - (*RelayOpenResult)(nil), // 197: openshell.v1.RelayOpenResult - (*RelayClose)(nil), // 198: openshell.v1.RelayClose - (*L7RequestSample)(nil), // 199: openshell.v1.L7RequestSample - (*DenialSummary)(nil), // 200: openshell.v1.DenialSummary - (*DenialGroupCount)(nil), // 201: openshell.v1.DenialGroupCount - (*NetworkActivitySummary)(nil), // 202: openshell.v1.NetworkActivitySummary - (*PolicyChunk)(nil), // 203: openshell.v1.PolicyChunk - (*DraftPolicyUpdate)(nil), // 204: openshell.v1.DraftPolicyUpdate - (*SubmitPolicyAnalysisRequest)(nil), // 205: openshell.v1.SubmitPolicyAnalysisRequest - (*SubmitPolicyAnalysisResponse)(nil), // 206: openshell.v1.SubmitPolicyAnalysisResponse - (*GetDraftPolicyRequest)(nil), // 207: openshell.v1.GetDraftPolicyRequest - (*GetDraftPolicyResponse)(nil), // 208: openshell.v1.GetDraftPolicyResponse - (*ApproveDraftChunkRequest)(nil), // 209: openshell.v1.ApproveDraftChunkRequest - (*ApproveDraftChunkResponse)(nil), // 210: openshell.v1.ApproveDraftChunkResponse - (*RejectDraftChunkRequest)(nil), // 211: openshell.v1.RejectDraftChunkRequest - (*RejectDraftChunkResponse)(nil), // 212: openshell.v1.RejectDraftChunkResponse - (*DraftChunkApproval)(nil), // 213: openshell.v1.DraftChunkApproval - (*ApproveAllDraftChunksRequest)(nil), // 214: openshell.v1.ApproveAllDraftChunksRequest - (*ApproveAllDraftChunksResponse)(nil), // 215: openshell.v1.ApproveAllDraftChunksResponse - (*EditDraftChunkRequest)(nil), // 216: openshell.v1.EditDraftChunkRequest - (*EditDraftChunkResponse)(nil), // 217: openshell.v1.EditDraftChunkResponse - (*UndoDraftChunkRequest)(nil), // 218: openshell.v1.UndoDraftChunkRequest - (*UndoDraftChunkResponse)(nil), // 219: openshell.v1.UndoDraftChunkResponse - (*ClearDraftChunksRequest)(nil), // 220: openshell.v1.ClearDraftChunksRequest - (*ClearDraftChunksResponse)(nil), // 221: openshell.v1.ClearDraftChunksResponse - (*GetDraftHistoryRequest)(nil), // 222: openshell.v1.GetDraftHistoryRequest - (*DraftHistoryEntry)(nil), // 223: openshell.v1.DraftHistoryEntry - (*GetDraftHistoryResponse)(nil), // 224: openshell.v1.GetDraftHistoryResponse - (*CreateWorkspaceRequest)(nil), // 225: openshell.v1.CreateWorkspaceRequest - (*CreateWorkspaceResponse)(nil), // 226: openshell.v1.CreateWorkspaceResponse - (*GetWorkspaceRequest)(nil), // 227: openshell.v1.GetWorkspaceRequest - (*GetWorkspaceResponse)(nil), // 228: openshell.v1.GetWorkspaceResponse - (*ListWorkspacesRequest)(nil), // 229: openshell.v1.ListWorkspacesRequest - (*ListWorkspacesResponse)(nil), // 230: openshell.v1.ListWorkspacesResponse - (*DeleteWorkspaceRequest)(nil), // 231: openshell.v1.DeleteWorkspaceRequest - (*DeleteWorkspaceResponse)(nil), // 232: openshell.v1.DeleteWorkspaceResponse - (*WorkspaceMember)(nil), // 233: openshell.v1.WorkspaceMember - (*AddWorkspaceMemberRequest)(nil), // 234: openshell.v1.AddWorkspaceMemberRequest - (*AddWorkspaceMemberResponse)(nil), // 235: openshell.v1.AddWorkspaceMemberResponse - (*RemoveWorkspaceMemberRequest)(nil), // 236: openshell.v1.RemoveWorkspaceMemberRequest - (*RemoveWorkspaceMemberResponse)(nil), // 237: openshell.v1.RemoveWorkspaceMemberResponse - (*ListWorkspaceMembersRequest)(nil), // 238: openshell.v1.ListWorkspaceMembersRequest - (*ListWorkspaceMembersResponse)(nil), // 239: openshell.v1.ListWorkspaceMembersResponse - (*ExtensionServiceCredential)(nil), // 240: openshell.v1.ExtensionServiceCredential - (*EndpointObservation)(nil), // 241: openshell.v1.EndpointObservation - (*ReportEndpointStatusRequest)(nil), // 242: openshell.v1.ReportEndpointStatusRequest - (*ReportEndpointStatusResponse)(nil), // 243: openshell.v1.ReportEndpointStatusResponse - (*EndpointStatus)(nil), // 244: openshell.v1.EndpointStatus - (*SandboxProvisioning)(nil), // 245: openshell.v1.SandboxProvisioning - (*SandboxServiceExposure)(nil), // 246: openshell.v1.SandboxServiceExposure - nil, // 247: openshell.v1.SandboxSpec.EnvironmentEntry - nil, // 248: openshell.v1.SandboxTemplate.LabelsEntry - nil, // 249: openshell.v1.SandboxTemplate.AnnotationsEntry - nil, // 250: openshell.v1.SandboxTemplate.EnvironmentEntry - nil, // 251: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry - nil, // 252: openshell.v1.PlatformEvent.MetadataEntry - nil, // 253: openshell.v1.CreateSandboxRequest.LabelsEntry - nil, // 254: openshell.v1.CreateSandboxRequest.AnnotationsEntry - nil, // 255: openshell.v1.SandboxResponse.ServiceUrlsEntry - nil, // 256: openshell.v1.ExecSandboxRequest.EnvironmentEntry - nil, // 257: openshell.v1.SandboxLogLine.FieldsEntry - nil, // 258: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - nil, // 259: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - nil, // 260: openshell.v1.ProviderProfile.AnnotationsEntry - nil, // 261: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - nil, // 262: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - nil, // 263: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - nil, // 264: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - nil, // 265: openshell.v1.UpdateConfigRequest.AnnotationsEntry - nil, // 266: openshell.v1.UpdateConfigResponse.AnnotationsEntry - nil, // 267: openshell.v1.SandboxPolicyRevision.ProvenanceEntry - nil, // 268: openshell.v1.CreateWorkspaceRequest.LabelsEntry - (*timestamppb.Timestamp)(nil), // 269: google.protobuf.Timestamp - (*datamodelv1.ObjectMeta)(nil), // 270: openshell.datamodel.v1.ObjectMeta - (*sandboxv1.SandboxPolicy)(nil), // 271: openshell.sandbox.v1.SandboxPolicy - (*structpb.Struct)(nil), // 272: google.protobuf.Struct - (*durationpb.Duration)(nil), // 273: google.protobuf.Duration - (*datamodelv1.WorkspaceSelector)(nil), // 274: openshell.datamodel.v1.WorkspaceSelector - (*datamodelv1.Provider)(nil), // 275: openshell.datamodel.v1.Provider - (sandboxv1.PolicySource)(0), // 276: openshell.sandbox.v1.PolicySource - (*sandboxv1.NetworkEndpoint)(nil), // 277: openshell.sandbox.v1.NetworkEndpoint - (*sandboxv1.NetworkBinary)(nil), // 278: openshell.sandbox.v1.NetworkBinary - (*sandboxv1.SettingValue)(nil), // 279: openshell.sandbox.v1.SettingValue - (*sandboxv1.NetworkPolicyRule)(nil), // 280: openshell.sandbox.v1.NetworkPolicyRule - (*sandboxv1.L7DenyRule)(nil), // 281: openshell.sandbox.v1.L7DenyRule - (*sandboxv1.L7Rule)(nil), // 282: openshell.sandbox.v1.L7Rule - (*datamodelv1.Workspace)(nil), // 283: openshell.datamodel.v1.Workspace - (*sandboxv1.GetSandboxConfigRequest)(nil), // 284: openshell.sandbox.v1.GetSandboxConfigRequest - (*sandboxv1.GetGatewayConfigRequest)(nil), // 285: openshell.sandbox.v1.GetGatewayConfigRequest - (*sandboxv1.GetSandboxConfigResponse)(nil), // 286: openshell.sandbox.v1.GetSandboxConfigResponse - (*sandboxv1.GetGatewayConfigResponse)(nil), // 287: openshell.sandbox.v1.GetGatewayConfigResponse + (*RegisterSupervisorRequest)(nil), // 18: openshell.v1.RegisterSupervisorRequest + (*RegisterSupervisorResponse)(nil), // 19: openshell.v1.RegisterSupervisorResponse + (*IssueSandboxTokenRequest)(nil), // 20: openshell.v1.IssueSandboxTokenRequest + (*IssueSandboxTokenResponse)(nil), // 21: openshell.v1.IssueSandboxTokenResponse + (*RefreshSandboxTokenRequest)(nil), // 22: openshell.v1.RefreshSandboxTokenRequest + (*RefreshSandboxTokenResponse)(nil), // 23: openshell.v1.RefreshSandboxTokenResponse + (*HealthRequest)(nil), // 24: openshell.v1.HealthRequest + (*HealthResponse)(nil), // 25: openshell.v1.HealthResponse + (*GetCurrentUserRequest)(nil), // 26: openshell.v1.GetCurrentUserRequest + (*GetCurrentUserResponse)(nil), // 27: openshell.v1.GetCurrentUserResponse + (*GetGatewayInfoRequest)(nil), // 28: openshell.v1.GetGatewayInfoRequest + (*GetGatewayInfoResponse)(nil), // 29: openshell.v1.GetGatewayInfoResponse + (*NegotiatedExtensionInfo)(nil), // 30: openshell.v1.NegotiatedExtensionInfo + (*ComputeDriverInfo)(nil), // 31: openshell.v1.ComputeDriverInfo + (*ComputeDriverCapabilities)(nil), // 32: openshell.v1.ComputeDriverCapabilities + (*ResourceCapabilities)(nil), // 33: openshell.v1.ResourceCapabilities + (*CpuResourceCapabilities)(nil), // 34: openshell.v1.CpuResourceCapabilities + (*MemoryResourceCapabilities)(nil), // 35: openshell.v1.MemoryResourceCapabilities + (*GpuResourceCapabilities)(nil), // 36: openshell.v1.GpuResourceCapabilities + (*Sandbox)(nil), // 37: openshell.v1.Sandbox + (*SandboxSpec)(nil), // 38: openshell.v1.SandboxSpec + (*ResourceRequirements)(nil), // 39: openshell.v1.ResourceRequirements + (*GpuResourceRequirements)(nil), // 40: openshell.v1.GpuResourceRequirements + (*SandboxTemplate)(nil), // 41: openshell.v1.SandboxTemplate + (*SandboxWorkloadTemplate)(nil), // 42: openshell.v1.SandboxWorkloadTemplate + (*SandboxWorkloadTemplateSpec)(nil), // 43: openshell.v1.SandboxWorkloadTemplateSpec + (*SandboxWorkloadConfig)(nil), // 44: openshell.v1.SandboxWorkloadConfig + (*SandboxResources)(nil), // 45: openshell.v1.SandboxResources + (*SandboxServiceLevel)(nil), // 46: openshell.v1.SandboxServiceLevel + (*SandboxStartup)(nil), // 47: openshell.v1.SandboxStartup + (*SandboxWorkloadTemplateProvenance)(nil), // 48: openshell.v1.SandboxWorkloadTemplateProvenance + (*SandboxStatus)(nil), // 49: openshell.v1.SandboxStatus + (*SandboxCondition)(nil), // 50: openshell.v1.SandboxCondition + (*PlatformEvent)(nil), // 51: openshell.v1.PlatformEvent + (*CreateSandboxRequest)(nil), // 52: openshell.v1.CreateSandboxRequest + (*CreateSandboxTemplateRequest)(nil), // 53: openshell.v1.CreateSandboxTemplateRequest + (*GetSandboxTemplateRequest)(nil), // 54: openshell.v1.GetSandboxTemplateRequest + (*ListSandboxTemplatesRequest)(nil), // 55: openshell.v1.ListSandboxTemplatesRequest + (*DeleteSandboxTemplateRequest)(nil), // 56: openshell.v1.DeleteSandboxTemplateRequest + (*SandboxTemplateResponse)(nil), // 57: openshell.v1.SandboxTemplateResponse + (*ListSandboxTemplatesResponse)(nil), // 58: openshell.v1.ListSandboxTemplatesResponse + (*DeleteSandboxTemplateResponse)(nil), // 59: openshell.v1.DeleteSandboxTemplateResponse + (*BeginRootfsTarStagingRequest)(nil), // 60: openshell.v1.BeginRootfsTarStagingRequest + (*BeginRootfsTarStagingResponse)(nil), // 61: openshell.v1.BeginRootfsTarStagingResponse + (*GetSandboxRequest)(nil), // 62: openshell.v1.GetSandboxRequest + (*ListSandboxesRequest)(nil), // 63: openshell.v1.ListSandboxesRequest + (*ListSandboxProvidersRequest)(nil), // 64: openshell.v1.ListSandboxProvidersRequest + (*AttachSandboxProviderRequest)(nil), // 65: openshell.v1.AttachSandboxProviderRequest + (*DetachSandboxProviderRequest)(nil), // 66: openshell.v1.DetachSandboxProviderRequest + (*DeleteSandboxRequest)(nil), // 67: openshell.v1.DeleteSandboxRequest + (*StopSandboxRequest)(nil), // 68: openshell.v1.StopSandboxRequest + (*StartSandboxRequest)(nil), // 69: openshell.v1.StartSandboxRequest + (*SandboxResponse)(nil), // 70: openshell.v1.SandboxResponse + (*ListSandboxesResponse)(nil), // 71: openshell.v1.ListSandboxesResponse + (*ListSandboxProvidersResponse)(nil), // 72: openshell.v1.ListSandboxProvidersResponse + (*AttachSandboxProviderResponse)(nil), // 73: openshell.v1.AttachSandboxProviderResponse + (*DetachSandboxProviderResponse)(nil), // 74: openshell.v1.DetachSandboxProviderResponse + (*ProviderDesiredIdentity)(nil), // 75: openshell.v1.ProviderDesiredIdentity + (*ConfigSnapshotRevision)(nil), // 76: openshell.v1.ConfigSnapshotRevision + (*SandboxConfigRevision)(nil), // 77: openshell.v1.SandboxConfigRevision + (*ConfigUpdateOperation)(nil), // 78: openshell.v1.ConfigUpdateOperation + (*ProviderMutationReceipt)(nil), // 79: openshell.v1.ProviderMutationReceipt + (*ProviderReadinessObservation)(nil), // 80: openshell.v1.ProviderReadinessObservation + (*ProviderReadinessStatus)(nil), // 81: openshell.v1.ProviderReadinessStatus + (*GetSandboxProviderStatusRequest)(nil), // 82: openshell.v1.GetSandboxProviderStatusRequest + (*GetSandboxProviderStatusResponse)(nil), // 83: openshell.v1.GetSandboxProviderStatusResponse + (*ReportProviderReadinessRequest)(nil), // 84: openshell.v1.ReportProviderReadinessRequest + (*ReportProviderReadinessResponse)(nil), // 85: openshell.v1.ReportProviderReadinessResponse + (*DeleteSandboxResponse)(nil), // 86: openshell.v1.DeleteSandboxResponse + (*CreateSshSessionRequest)(nil), // 87: openshell.v1.CreateSshSessionRequest + (*CreateSshSessionResponse)(nil), // 88: openshell.v1.CreateSshSessionResponse + (*ExposeServiceRequest)(nil), // 89: openshell.v1.ExposeServiceRequest + (*GetServiceRequest)(nil), // 90: openshell.v1.GetServiceRequest + (*ListServicesRequest)(nil), // 91: openshell.v1.ListServicesRequest + (*ListServicesResponse)(nil), // 92: openshell.v1.ListServicesResponse + (*DeleteServiceRequest)(nil), // 93: openshell.v1.DeleteServiceRequest + (*DeleteServiceResponse)(nil), // 94: openshell.v1.DeleteServiceResponse + (*ServiceEndpoint)(nil), // 95: openshell.v1.ServiceEndpoint + (*ServiceEndpointResponse)(nil), // 96: openshell.v1.ServiceEndpointResponse + (*RevokeSshSessionRequest)(nil), // 97: openshell.v1.RevokeSshSessionRequest + (*RevokeSshSessionResponse)(nil), // 98: openshell.v1.RevokeSshSessionResponse + (*ExecSandboxRequest)(nil), // 99: openshell.v1.ExecSandboxRequest + (*ExecSandboxStdout)(nil), // 100: openshell.v1.ExecSandboxStdout + (*ExecSandboxStderr)(nil), // 101: openshell.v1.ExecSandboxStderr + (*ExecSandboxExit)(nil), // 102: openshell.v1.ExecSandboxExit + (*ExecSandboxEvent)(nil), // 103: openshell.v1.ExecSandboxEvent + (*TcpForwardInit)(nil), // 104: openshell.v1.TcpForwardInit + (*TcpForwardFrame)(nil), // 105: openshell.v1.TcpForwardFrame + (*ExecSandboxInput)(nil), // 106: openshell.v1.ExecSandboxInput + (*ExecSandboxWindowResize)(nil), // 107: openshell.v1.ExecSandboxWindowResize + (*SshSession)(nil), // 108: openshell.v1.SshSession + (*WatchSandboxRequest)(nil), // 109: openshell.v1.WatchSandboxRequest + (*SandboxStreamEvent)(nil), // 110: openshell.v1.SandboxStreamEvent + (*SandboxLogLine)(nil), // 111: openshell.v1.SandboxLogLine + (*SandboxStreamWarning)(nil), // 112: openshell.v1.SandboxStreamWarning + (*CreateProviderRequest)(nil), // 113: openshell.v1.CreateProviderRequest + (*GetProviderRequest)(nil), // 114: openshell.v1.GetProviderRequest + (*ListProvidersRequest)(nil), // 115: openshell.v1.ListProvidersRequest + (*UpdateProviderRequest)(nil), // 116: openshell.v1.UpdateProviderRequest + (*DeleteProviderRequest)(nil), // 117: openshell.v1.DeleteProviderRequest + (*ProviderResponse)(nil), // 118: openshell.v1.ProviderResponse + (*ListProvidersResponse)(nil), // 119: openshell.v1.ListProvidersResponse + (*ListProviderProfilesRequest)(nil), // 120: openshell.v1.ListProviderProfilesRequest + (*GetProviderProfileRequest)(nil), // 121: openshell.v1.GetProviderProfileRequest + (*ProviderProfileImportItem)(nil), // 122: openshell.v1.ProviderProfileImportItem + (*ProviderProfileDiagnostic)(nil), // 123: openshell.v1.ProviderProfileDiagnostic + (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 124: openshell.v1.ProviderCredentialTokenGrantAudienceOverride + (*ProviderCredentialTokenGrantSubjectToken)(nil), // 125: openshell.v1.ProviderCredentialTokenGrantSubjectToken + (*ProviderCredentialTokenGrant)(nil), // 126: openshell.v1.ProviderCredentialTokenGrant + (*ProviderProfileCredential)(nil), // 127: openshell.v1.ProviderProfileCredential + (*ProviderCredentialRefreshMaterial)(nil), // 128: openshell.v1.ProviderCredentialRefreshMaterial + (*ProviderCredentialRefreshOutput)(nil), // 129: openshell.v1.ProviderCredentialRefreshOutput + (*ProviderCredentialRefresh)(nil), // 130: openshell.v1.ProviderCredentialRefresh + (*ProviderCredentialRefreshStatus)(nil), // 131: openshell.v1.ProviderCredentialRefreshStatus + (*ProviderProfileDiscovery)(nil), // 132: openshell.v1.ProviderProfileDiscovery + (*GetProviderRefreshStatusRequest)(nil), // 133: openshell.v1.GetProviderRefreshStatusRequest + (*GetProviderRefreshStatusResponse)(nil), // 134: openshell.v1.GetProviderRefreshStatusResponse + (*ConfigureProviderRefreshRequest)(nil), // 135: openshell.v1.ConfigureProviderRefreshRequest + (*ConfigureProviderRefreshResponse)(nil), // 136: openshell.v1.ConfigureProviderRefreshResponse + (*RotateProviderCredentialRequest)(nil), // 137: openshell.v1.RotateProviderCredentialRequest + (*RotateProviderCredentialResponse)(nil), // 138: openshell.v1.RotateProviderCredentialResponse + (*DeleteProviderRefreshRequest)(nil), // 139: openshell.v1.DeleteProviderRefreshRequest + (*DeleteProviderRefreshResponse)(nil), // 140: openshell.v1.DeleteProviderRefreshResponse + (*ProviderProfile)(nil), // 141: openshell.v1.ProviderProfile + (*ProviderProfileResponse)(nil), // 142: openshell.v1.ProviderProfileResponse + (*ListProviderProfilesResponse)(nil), // 143: openshell.v1.ListProviderProfilesResponse + (*ImportProviderProfilesRequest)(nil), // 144: openshell.v1.ImportProviderProfilesRequest + (*ImportProviderProfilesResponse)(nil), // 145: openshell.v1.ImportProviderProfilesResponse + (*UpdateProviderProfilesRequest)(nil), // 146: openshell.v1.UpdateProviderProfilesRequest + (*UpdateProviderProfilesResponse)(nil), // 147: openshell.v1.UpdateProviderProfilesResponse + (*LintProviderProfilesRequest)(nil), // 148: openshell.v1.LintProviderProfilesRequest + (*LintProviderProfilesResponse)(nil), // 149: openshell.v1.LintProviderProfilesResponse + (*DeleteProviderResponse)(nil), // 150: openshell.v1.DeleteProviderResponse + (*DeleteProviderProfileRequest)(nil), // 151: openshell.v1.DeleteProviderProfileRequest + (*DeleteProviderProfileResponse)(nil), // 152: openshell.v1.DeleteProviderProfileResponse + (*GetSandboxProviderEnvironmentRequest)(nil), // 153: openshell.v1.GetSandboxProviderEnvironmentRequest + (*StaticCredentialEndpointBinding)(nil), // 154: openshell.v1.StaticCredentialEndpointBinding + (*StaticCredentialBinding)(nil), // 155: openshell.v1.StaticCredentialBinding + (*GetSandboxProviderEnvironmentResponse)(nil), // 156: openshell.v1.GetSandboxProviderEnvironmentResponse + (*ExchangeProviderSubjectTokenRequest)(nil), // 157: openshell.v1.ExchangeProviderSubjectTokenRequest + (*ExchangeProviderSubjectTokenResponse)(nil), // 158: openshell.v1.ExchangeProviderSubjectTokenResponse + (*UpdateConfigRequest)(nil), // 159: openshell.v1.UpdateConfigRequest + (*PolicyMergeOperation)(nil), // 160: openshell.v1.PolicyMergeOperation + (*AddNetworkRule)(nil), // 161: openshell.v1.AddNetworkRule + (*RemoveNetworkEndpoint)(nil), // 162: openshell.v1.RemoveNetworkEndpoint + (*RemoveNetworkRule)(nil), // 163: openshell.v1.RemoveNetworkRule + (*L7RuleTarget)(nil), // 164: openshell.v1.L7RuleTarget + (*AddDenyRules)(nil), // 165: openshell.v1.AddDenyRules + (*AddAllowRules)(nil), // 166: openshell.v1.AddAllowRules + (*RemoveNetworkBinary)(nil), // 167: openshell.v1.RemoveNetworkBinary + (*UpdateConfigResponse)(nil), // 168: openshell.v1.UpdateConfigResponse + (*GetSandboxPolicyStatusRequest)(nil), // 169: openshell.v1.GetSandboxPolicyStatusRequest + (*GetSandboxPolicyStatusResponse)(nil), // 170: openshell.v1.GetSandboxPolicyStatusResponse + (*ListSandboxPoliciesRequest)(nil), // 171: openshell.v1.ListSandboxPoliciesRequest + (*ListSandboxPoliciesResponse)(nil), // 172: openshell.v1.ListSandboxPoliciesResponse + (*ReportPolicyStatusRequest)(nil), // 173: openshell.v1.ReportPolicyStatusRequest + (*ReportPolicyStatusResponse)(nil), // 174: openshell.v1.ReportPolicyStatusResponse + (*SandboxConfigurationAdmission)(nil), // 175: openshell.v1.SandboxConfigurationAdmission + (*ReportSandboxConfigurationRequest)(nil), // 176: openshell.v1.ReportSandboxConfigurationRequest + (*ReportSandboxConfigurationResponse)(nil), // 177: openshell.v1.ReportSandboxConfigurationResponse + (*SandboxPolicyRevision)(nil), // 178: openshell.v1.SandboxPolicyRevision + (*GetSandboxLogsRequest)(nil), // 179: openshell.v1.GetSandboxLogsRequest + (*PushSandboxLogsRequest)(nil), // 180: openshell.v1.PushSandboxLogsRequest + (*PushSandboxLogsResponse)(nil), // 181: openshell.v1.PushSandboxLogsResponse + (*GetSandboxLogsResponse)(nil), // 182: openshell.v1.GetSandboxLogsResponse + (*SupervisorMessage)(nil), // 183: openshell.v1.SupervisorMessage + (*GatewayMessage)(nil), // 184: openshell.v1.GatewayMessage + (*SupervisorHello)(nil), // 185: openshell.v1.SupervisorHello + (*SessionAccepted)(nil), // 186: openshell.v1.SessionAccepted + (*SessionRejected)(nil), // 187: openshell.v1.SessionRejected + (*SupervisorHeartbeat)(nil), // 188: openshell.v1.SupervisorHeartbeat + (*GatewayHeartbeat)(nil), // 189: openshell.v1.GatewayHeartbeat + (*ReportMainProcessExitRequest)(nil), // 190: openshell.v1.ReportMainProcessExitRequest + (*ReportMainProcessExitResponse)(nil), // 191: openshell.v1.ReportMainProcessExitResponse + (*FinalizeMainProcessExitRequest)(nil), // 192: openshell.v1.FinalizeMainProcessExitRequest + (*FinalizeMainProcessExitResponse)(nil), // 193: openshell.v1.FinalizeMainProcessExitResponse + (*RelayOpen)(nil), // 194: openshell.v1.RelayOpen + (*SshRelayTarget)(nil), // 195: openshell.v1.SshRelayTarget + (*TcpRelayTarget)(nil), // 196: openshell.v1.TcpRelayTarget + (*RelayInit)(nil), // 197: openshell.v1.RelayInit + (*RelayFrame)(nil), // 198: openshell.v1.RelayFrame + (*RelayOpenResult)(nil), // 199: openshell.v1.RelayOpenResult + (*RelayClose)(nil), // 200: openshell.v1.RelayClose + (*L7RequestSample)(nil), // 201: openshell.v1.L7RequestSample + (*DenialSummary)(nil), // 202: openshell.v1.DenialSummary + (*DenialGroupCount)(nil), // 203: openshell.v1.DenialGroupCount + (*NetworkActivitySummary)(nil), // 204: openshell.v1.NetworkActivitySummary + (*PolicyChunk)(nil), // 205: openshell.v1.PolicyChunk + (*DraftPolicyUpdate)(nil), // 206: openshell.v1.DraftPolicyUpdate + (*SubmitPolicyAnalysisRequest)(nil), // 207: openshell.v1.SubmitPolicyAnalysisRequest + (*SubmitPolicyAnalysisResponse)(nil), // 208: openshell.v1.SubmitPolicyAnalysisResponse + (*GetDraftPolicyRequest)(nil), // 209: openshell.v1.GetDraftPolicyRequest + (*GetDraftPolicyResponse)(nil), // 210: openshell.v1.GetDraftPolicyResponse + (*ApproveDraftChunkRequest)(nil), // 211: openshell.v1.ApproveDraftChunkRequest + (*ApproveDraftChunkResponse)(nil), // 212: openshell.v1.ApproveDraftChunkResponse + (*RejectDraftChunkRequest)(nil), // 213: openshell.v1.RejectDraftChunkRequest + (*RejectDraftChunkResponse)(nil), // 214: openshell.v1.RejectDraftChunkResponse + (*DraftChunkApproval)(nil), // 215: openshell.v1.DraftChunkApproval + (*ApproveAllDraftChunksRequest)(nil), // 216: openshell.v1.ApproveAllDraftChunksRequest + (*ApproveAllDraftChunksResponse)(nil), // 217: openshell.v1.ApproveAllDraftChunksResponse + (*EditDraftChunkRequest)(nil), // 218: openshell.v1.EditDraftChunkRequest + (*EditDraftChunkResponse)(nil), // 219: openshell.v1.EditDraftChunkResponse + (*UndoDraftChunkRequest)(nil), // 220: openshell.v1.UndoDraftChunkRequest + (*UndoDraftChunkResponse)(nil), // 221: openshell.v1.UndoDraftChunkResponse + (*ClearDraftChunksRequest)(nil), // 222: openshell.v1.ClearDraftChunksRequest + (*ClearDraftChunksResponse)(nil), // 223: openshell.v1.ClearDraftChunksResponse + (*GetDraftHistoryRequest)(nil), // 224: openshell.v1.GetDraftHistoryRequest + (*DraftHistoryEntry)(nil), // 225: openshell.v1.DraftHistoryEntry + (*GetDraftHistoryResponse)(nil), // 226: openshell.v1.GetDraftHistoryResponse + (*CreateWorkspaceRequest)(nil), // 227: openshell.v1.CreateWorkspaceRequest + (*CreateWorkspaceResponse)(nil), // 228: openshell.v1.CreateWorkspaceResponse + (*GetWorkspaceRequest)(nil), // 229: openshell.v1.GetWorkspaceRequest + (*GetWorkspaceResponse)(nil), // 230: openshell.v1.GetWorkspaceResponse + (*ListWorkspacesRequest)(nil), // 231: openshell.v1.ListWorkspacesRequest + (*ListWorkspacesResponse)(nil), // 232: openshell.v1.ListWorkspacesResponse + (*DeleteWorkspaceRequest)(nil), // 233: openshell.v1.DeleteWorkspaceRequest + (*DeleteWorkspaceResponse)(nil), // 234: openshell.v1.DeleteWorkspaceResponse + (*WorkspaceMember)(nil), // 235: openshell.v1.WorkspaceMember + (*AddWorkspaceMemberRequest)(nil), // 236: openshell.v1.AddWorkspaceMemberRequest + (*AddWorkspaceMemberResponse)(nil), // 237: openshell.v1.AddWorkspaceMemberResponse + (*RemoveWorkspaceMemberRequest)(nil), // 238: openshell.v1.RemoveWorkspaceMemberRequest + (*RemoveWorkspaceMemberResponse)(nil), // 239: openshell.v1.RemoveWorkspaceMemberResponse + (*ListWorkspaceMembersRequest)(nil), // 240: openshell.v1.ListWorkspaceMembersRequest + (*ListWorkspaceMembersResponse)(nil), // 241: openshell.v1.ListWorkspaceMembersResponse + (*ExtensionServiceCredential)(nil), // 242: openshell.v1.ExtensionServiceCredential + (*EndpointObservation)(nil), // 243: openshell.v1.EndpointObservation + (*ReportEndpointStatusRequest)(nil), // 244: openshell.v1.ReportEndpointStatusRequest + (*ReportEndpointStatusResponse)(nil), // 245: openshell.v1.ReportEndpointStatusResponse + (*EndpointStatus)(nil), // 246: openshell.v1.EndpointStatus + (*SandboxProvisioning)(nil), // 247: openshell.v1.SandboxProvisioning + (*SandboxServiceExposure)(nil), // 248: openshell.v1.SandboxServiceExposure + nil, // 249: openshell.v1.SandboxSpec.EnvironmentEntry + nil, // 250: openshell.v1.SandboxTemplate.LabelsEntry + nil, // 251: openshell.v1.SandboxTemplate.AnnotationsEntry + nil, // 252: openshell.v1.SandboxTemplate.EnvironmentEntry + nil, // 253: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + nil, // 254: openshell.v1.PlatformEvent.MetadataEntry + nil, // 255: openshell.v1.CreateSandboxRequest.LabelsEntry + nil, // 256: openshell.v1.CreateSandboxRequest.AnnotationsEntry + nil, // 257: openshell.v1.SandboxResponse.ServiceUrlsEntry + nil, // 258: openshell.v1.ExecSandboxRequest.EnvironmentEntry + nil, // 259: openshell.v1.SandboxLogLine.FieldsEntry + nil, // 260: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + nil, // 261: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + nil, // 262: openshell.v1.ProviderProfile.AnnotationsEntry + nil, // 263: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + nil, // 264: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + nil, // 265: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + nil, // 266: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + nil, // 267: openshell.v1.UpdateConfigRequest.AnnotationsEntry + nil, // 268: openshell.v1.UpdateConfigResponse.AnnotationsEntry + nil, // 269: openshell.v1.SandboxPolicyRevision.ProvenanceEntry + nil, // 270: openshell.v1.CreateWorkspaceRequest.LabelsEntry + (*timestamppb.Timestamp)(nil), // 271: google.protobuf.Timestamp + (*datamodelv1.ObjectMeta)(nil), // 272: openshell.datamodel.v1.ObjectMeta + (*sandboxv1.SandboxPolicy)(nil), // 273: openshell.sandbox.v1.SandboxPolicy + (*structpb.Struct)(nil), // 274: google.protobuf.Struct + (*durationpb.Duration)(nil), // 275: google.protobuf.Duration + (*datamodelv1.WorkspaceSelector)(nil), // 276: openshell.datamodel.v1.WorkspaceSelector + (*datamodelv1.Provider)(nil), // 277: openshell.datamodel.v1.Provider + (sandboxv1.PolicySource)(0), // 278: openshell.sandbox.v1.PolicySource + (*sandboxv1.NetworkEndpoint)(nil), // 279: openshell.sandbox.v1.NetworkEndpoint + (*sandboxv1.NetworkBinary)(nil), // 280: openshell.sandbox.v1.NetworkBinary + (*sandboxv1.SettingValue)(nil), // 281: openshell.sandbox.v1.SettingValue + (*sandboxv1.NetworkPolicyRule)(nil), // 282: openshell.sandbox.v1.NetworkPolicyRule + (*sandboxv1.L7DenyRule)(nil), // 283: openshell.sandbox.v1.L7DenyRule + (*sandboxv1.L7Rule)(nil), // 284: openshell.sandbox.v1.L7Rule + (*datamodelv1.Workspace)(nil), // 285: openshell.datamodel.v1.Workspace + (*sandboxv1.GetSandboxConfigRequest)(nil), // 286: openshell.sandbox.v1.GetSandboxConfigRequest + (*sandboxv1.GetGatewayConfigRequest)(nil), // 287: openshell.sandbox.v1.GetGatewayConfigRequest + (*sandboxv1.GetSandboxConfigResponse)(nil), // 288: openshell.sandbox.v1.GetSandboxConfigResponse + (*sandboxv1.GetGatewayConfigResponse)(nil), // 289: openshell.sandbox.v1.GetGatewayConfigResponse } var file_openshell_proto_depIdxs = []int32{ - 269, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp - 269, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp - 240, // 2: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential - 269, // 3: openshell.v1.RefreshSandboxTokenResponse.sandbox_expiration_time:type_name -> google.protobuf.Timestamp + 271, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp + 271, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp + 242, // 2: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential + 271, // 3: openshell.v1.RefreshSandboxTokenResponse.sandbox_expiration_time:type_name -> google.protobuf.Timestamp 13, // 4: openshell.v1.HealthResponse.status:type_name -> openshell.v1.ServiceStatus 13, // 5: openshell.v1.GetGatewayInfoResponse.status:type_name -> openshell.v1.ServiceStatus - 29, // 6: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo - 28, // 7: openshell.v1.GetGatewayInfoResponse.extensions:type_name -> openshell.v1.NegotiatedExtensionInfo + 31, // 6: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo + 30, // 7: openshell.v1.GetGatewayInfoResponse.extensions:type_name -> openshell.v1.NegotiatedExtensionInfo 0, // 8: openshell.v1.NegotiatedExtensionInfo.kind:type_name -> openshell.v1.ExtensionKind - 30, // 9: openshell.v1.ComputeDriverInfo.capabilities:type_name -> openshell.v1.ComputeDriverCapabilities - 31, // 10: openshell.v1.ComputeDriverCapabilities.resource_capabilities:type_name -> openshell.v1.ResourceCapabilities - 32, // 11: openshell.v1.ResourceCapabilities.cpu:type_name -> openshell.v1.CpuResourceCapabilities - 33, // 12: openshell.v1.ResourceCapabilities.memory:type_name -> openshell.v1.MemoryResourceCapabilities - 34, // 13: openshell.v1.ResourceCapabilities.gpu:type_name -> openshell.v1.GpuResourceCapabilities - 270, // 14: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 36, // 15: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec - 47, // 16: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus - 46, // 17: openshell.v1.Sandbox.created_from_workload_template:type_name -> openshell.v1.SandboxWorkloadTemplateProvenance - 247, // 18: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry - 39, // 19: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate - 271, // 20: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 37, // 21: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements - 38, // 22: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements - 248, // 23: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry - 249, // 24: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry - 250, // 25: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry - 272, // 26: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct - 272, // 27: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct - 270, // 28: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 41, // 29: openshell.v1.SandboxWorkloadTemplate.spec:type_name -> openshell.v1.SandboxWorkloadTemplateSpec - 42, // 30: openshell.v1.SandboxWorkloadTemplateSpec.workload:type_name -> openshell.v1.SandboxWorkloadConfig - 272, // 31: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct - 44, // 32: openshell.v1.SandboxWorkloadTemplateSpec.desired_service_level:type_name -> openshell.v1.SandboxServiceLevel - 251, // 33: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry - 43, // 34: openshell.v1.SandboxWorkloadConfig.resources:type_name -> openshell.v1.SandboxResources - 38, // 35: openshell.v1.SandboxResources.gpu:type_name -> openshell.v1.GpuResourceRequirements - 45, // 36: openshell.v1.SandboxServiceLevel.startup:type_name -> openshell.v1.SandboxStartup - 273, // 37: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration - 48, // 38: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition + 32, // 9: openshell.v1.ComputeDriverInfo.capabilities:type_name -> openshell.v1.ComputeDriverCapabilities + 33, // 10: openshell.v1.ComputeDriverCapabilities.resource_capabilities:type_name -> openshell.v1.ResourceCapabilities + 34, // 11: openshell.v1.ResourceCapabilities.cpu:type_name -> openshell.v1.CpuResourceCapabilities + 35, // 12: openshell.v1.ResourceCapabilities.memory:type_name -> openshell.v1.MemoryResourceCapabilities + 36, // 13: openshell.v1.ResourceCapabilities.gpu:type_name -> openshell.v1.GpuResourceCapabilities + 272, // 14: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 38, // 15: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec + 49, // 16: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus + 48, // 17: openshell.v1.Sandbox.created_from_workload_template:type_name -> openshell.v1.SandboxWorkloadTemplateProvenance + 249, // 18: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry + 41, // 19: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate + 273, // 20: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 39, // 21: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements + 40, // 22: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements + 250, // 23: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry + 251, // 24: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry + 252, // 25: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry + 274, // 26: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct + 274, // 27: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct + 272, // 28: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 43, // 29: openshell.v1.SandboxWorkloadTemplate.spec:type_name -> openshell.v1.SandboxWorkloadTemplateSpec + 44, // 30: openshell.v1.SandboxWorkloadTemplateSpec.workload:type_name -> openshell.v1.SandboxWorkloadConfig + 274, // 31: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct + 46, // 32: openshell.v1.SandboxWorkloadTemplateSpec.desired_service_level:type_name -> openshell.v1.SandboxServiceLevel + 253, // 33: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + 45, // 34: openshell.v1.SandboxWorkloadConfig.resources:type_name -> openshell.v1.SandboxResources + 40, // 35: openshell.v1.SandboxResources.gpu:type_name -> openshell.v1.GpuResourceRequirements + 47, // 36: openshell.v1.SandboxServiceLevel.startup:type_name -> openshell.v1.SandboxStartup + 275, // 37: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration + 50, // 38: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition 1, // 39: openshell.v1.SandboxStatus.phase:type_name -> openshell.v1.SandboxPhase - 244, // 40: openshell.v1.SandboxStatus.endpoint_statuses:type_name -> openshell.v1.EndpointStatus - 173, // 41: openshell.v1.SandboxStatus.configuration_admission:type_name -> openshell.v1.SandboxConfigurationAdmission - 245, // 42: openshell.v1.SandboxStatus.provisioning:type_name -> openshell.v1.SandboxProvisioning - 269, // 43: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp - 269, // 44: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp - 252, // 45: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry - 274, // 46: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 36, // 47: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec - 253, // 48: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry - 254, // 49: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry - 246, // 50: openshell.v1.CreateSandboxRequest.service_exposures:type_name -> openshell.v1.SandboxServiceExposure - 274, // 51: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 40, // 52: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate - 274, // 53: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 54: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 55: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 40, // 56: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate - 40, // 57: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate + 246, // 40: openshell.v1.SandboxStatus.endpoint_statuses:type_name -> openshell.v1.EndpointStatus + 175, // 41: openshell.v1.SandboxStatus.configuration_admission:type_name -> openshell.v1.SandboxConfigurationAdmission + 247, // 42: openshell.v1.SandboxStatus.provisioning:type_name -> openshell.v1.SandboxProvisioning + 271, // 43: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp + 271, // 44: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp + 254, // 45: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry + 276, // 46: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 38, // 47: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec + 255, // 48: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry + 256, // 49: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry + 248, // 50: openshell.v1.CreateSandboxRequest.service_exposures:type_name -> openshell.v1.SandboxServiceExposure + 276, // 51: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 42, // 52: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate + 276, // 53: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 54: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 55: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 42, // 56: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate + 42, // 57: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate 16, // 58: openshell.v1.DeleteSandboxTemplateResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 274, // 59: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 269, // 60: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp - 274, // 61: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 62: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 63: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 64: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 65: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 66: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 67: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 68: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 35, // 69: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox - 255, // 70: openshell.v1.SandboxResponse.service_urls:type_name -> openshell.v1.SandboxResponse.ServiceUrlsEntry - 35, // 71: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox - 275, // 72: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 35, // 73: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 77, // 74: openshell.v1.AttachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt - 35, // 75: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 77, // 76: openshell.v1.DetachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt - 75, // 77: openshell.v1.ConfigSnapshotRevision.sandbox_config:type_name -> openshell.v1.SandboxConfigRevision - 73, // 78: openshell.v1.ConfigSnapshotRevision.provider_target:type_name -> openshell.v1.ProviderDesiredIdentity - 276, // 79: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource + 276, // 59: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 271, // 60: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp + 276, // 61: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 62: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 63: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 64: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 65: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 66: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 67: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 68: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 37, // 69: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox + 257, // 70: openshell.v1.SandboxResponse.service_urls:type_name -> openshell.v1.SandboxResponse.ServiceUrlsEntry + 37, // 71: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox + 277, // 72: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 37, // 73: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 79, // 74: openshell.v1.AttachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt + 37, // 75: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 79, // 76: openshell.v1.DetachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt + 77, // 77: openshell.v1.ConfigSnapshotRevision.sandbox_config:type_name -> openshell.v1.SandboxConfigRevision + 75, // 78: openshell.v1.ConfigSnapshotRevision.provider_target:type_name -> openshell.v1.ProviderDesiredIdentity + 278, // 79: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource 5, // 80: openshell.v1.ConfigUpdateOperation.component:type_name -> openshell.v1.ConfigComponent - 74, // 81: openshell.v1.ConfigUpdateOperation.target_revision:type_name -> openshell.v1.ConfigSnapshotRevision + 76, // 81: openshell.v1.ConfigUpdateOperation.target_revision:type_name -> openshell.v1.ConfigSnapshotRevision 7, // 82: openshell.v1.ConfigUpdateOperation.state:type_name -> openshell.v1.ConfigUpdateOperationState 6, // 83: openshell.v1.ConfigUpdateOperation.outcome:type_name -> openshell.v1.ConfigApplyOutcome - 269, // 84: openshell.v1.ConfigUpdateOperation.created_time:type_name -> google.protobuf.Timestamp - 269, // 85: openshell.v1.ConfigUpdateOperation.updated_time:type_name -> google.protobuf.Timestamp - 269, // 86: openshell.v1.ConfigUpdateOperation.completed_time:type_name -> google.protobuf.Timestamp + 271, // 84: openshell.v1.ConfigUpdateOperation.created_time:type_name -> google.protobuf.Timestamp + 271, // 85: openshell.v1.ConfigUpdateOperation.updated_time:type_name -> google.protobuf.Timestamp + 271, // 86: openshell.v1.ConfigUpdateOperation.completed_time:type_name -> google.protobuf.Timestamp 2, // 87: openshell.v1.ProviderMutationReceipt.kind:type_name -> openshell.v1.ProviderMutationKind - 73, // 88: openshell.v1.ProviderMutationReceipt.desired:type_name -> openshell.v1.ProviderDesiredIdentity - 269, // 89: openshell.v1.ProviderMutationReceipt.persisted_time:type_name -> google.protobuf.Timestamp + 75, // 88: openshell.v1.ProviderMutationReceipt.desired:type_name -> openshell.v1.ProviderDesiredIdentity + 271, // 89: openshell.v1.ProviderMutationReceipt.persisted_time:type_name -> google.protobuf.Timestamp 4, // 90: openshell.v1.ProviderReadinessObservation.reason:type_name -> openshell.v1.ProviderReadinessReason - 77, // 91: openshell.v1.ProviderReadinessStatus.receipt:type_name -> openshell.v1.ProviderMutationReceipt + 79, // 91: openshell.v1.ProviderReadinessStatus.receipt:type_name -> openshell.v1.ProviderMutationReceipt 3, // 92: openshell.v1.ProviderReadinessStatus.state:type_name -> openshell.v1.ProviderReadinessState 4, // 93: openshell.v1.ProviderReadinessStatus.reason:type_name -> openshell.v1.ProviderReadinessReason - 78, // 94: openshell.v1.ProviderReadinessStatus.observed:type_name -> openshell.v1.ProviderReadinessObservation - 269, // 95: openshell.v1.ProviderReadinessStatus.observed_time:type_name -> google.protobuf.Timestamp - 269, // 96: openshell.v1.ProviderReadinessStatus.evaluated_time:type_name -> google.protobuf.Timestamp - 76, // 97: openshell.v1.ProviderReadinessStatus.operation:type_name -> openshell.v1.ConfigUpdateOperation - 274, // 98: openshell.v1.GetSandboxProviderStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 79, // 99: openshell.v1.GetSandboxProviderStatusResponse.status:type_name -> openshell.v1.ProviderReadinessStatus - 78, // 100: openshell.v1.ReportProviderReadinessRequest.observation:type_name -> openshell.v1.ProviderReadinessObservation - 273, // 101: openshell.v1.ReportProviderReadinessResponse.report_interval:type_name -> google.protobuf.Duration - 273, // 102: openshell.v1.ReportProviderReadinessResponse.observation_ttl:type_name -> google.protobuf.Duration + 80, // 94: openshell.v1.ProviderReadinessStatus.observed:type_name -> openshell.v1.ProviderReadinessObservation + 271, // 95: openshell.v1.ProviderReadinessStatus.observed_time:type_name -> google.protobuf.Timestamp + 271, // 96: openshell.v1.ProviderReadinessStatus.evaluated_time:type_name -> google.protobuf.Timestamp + 78, // 97: openshell.v1.ProviderReadinessStatus.operation:type_name -> openshell.v1.ConfigUpdateOperation + 276, // 98: openshell.v1.GetSandboxProviderStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 81, // 99: openshell.v1.GetSandboxProviderStatusResponse.status:type_name -> openshell.v1.ProviderReadinessStatus + 80, // 100: openshell.v1.ReportProviderReadinessRequest.observation:type_name -> openshell.v1.ProviderReadinessObservation + 275, // 101: openshell.v1.ReportProviderReadinessResponse.report_interval:type_name -> google.protobuf.Duration + 275, // 102: openshell.v1.ReportProviderReadinessResponse.observation_ttl:type_name -> google.protobuf.Duration 16, // 103: openshell.v1.DeleteSandboxResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 274, // 104: openshell.v1.CreateSshSessionRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 269, // 105: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp - 274, // 106: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 107: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 108: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 94, // 109: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse - 274, // 110: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 104: openshell.v1.CreateSshSessionRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 271, // 105: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp + 276, // 106: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 107: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 108: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 96, // 109: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse + 276, // 110: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 16, // 111: openshell.v1.DeleteServiceResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 270, // 112: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 93, // 113: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint + 272, // 112: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 95, // 113: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint 16, // 114: openshell.v1.RevokeSshSessionResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 274, // 115: openshell.v1.ExecSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 256, // 116: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry - 273, // 117: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration - 98, // 118: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout - 99, // 119: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr - 100, // 120: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit - 193, // 121: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget - 194, // 122: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget - 102, // 123: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit - 97, // 124: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest - 105, // 125: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize - 270, // 126: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 269, // 127: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp - 274, // 128: openshell.v1.WatchSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 269, // 129: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp - 35, // 130: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox - 109, // 131: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine - 49, // 132: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent - 110, // 133: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning - 204, // 134: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate - 269, // 135: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp - 257, // 136: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry - 274, // 137: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 275, // 138: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 274, // 139: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 140: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 141: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 275, // 142: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 258, // 143: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - 274, // 144: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 275, // 145: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider - 77, // 146: openshell.v1.ProviderResponse.target_receipts:type_name -> openshell.v1.ProviderMutationReceipt - 275, // 147: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 274, // 148: openshell.v1.ListProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 149: openshell.v1.GetProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 139, // 150: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile - 273, // 151: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration - 122, // 152: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride + 276, // 115: openshell.v1.ExecSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 258, // 116: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry + 275, // 117: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration + 100, // 118: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout + 101, // 119: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr + 102, // 120: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit + 195, // 121: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget + 196, // 122: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget + 104, // 123: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit + 99, // 124: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest + 107, // 125: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize + 272, // 126: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 271, // 127: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp + 276, // 128: openshell.v1.WatchSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 271, // 129: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp + 37, // 130: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox + 111, // 131: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine + 51, // 132: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent + 112, // 133: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning + 206, // 134: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate + 271, // 135: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp + 259, // 136: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry + 276, // 137: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 277, // 138: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 276, // 139: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 140: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 141: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 277, // 142: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 260, // 143: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + 276, // 144: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 277, // 145: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider + 79, // 146: openshell.v1.ProviderResponse.target_receipts:type_name -> openshell.v1.ProviderMutationReceipt + 277, // 147: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 276, // 148: openshell.v1.ListProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 149: openshell.v1.GetProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 141, // 150: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile + 275, // 151: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration + 124, // 152: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride 8, // 153: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType - 123, // 154: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken - 128, // 155: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh - 124, // 156: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant + 125, // 154: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken + 130, // 155: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh + 126, // 156: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant 9, // 157: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 273, // 158: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration - 273, // 159: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration - 126, // 160: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial - 127, // 161: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput + 275, // 158: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration + 275, // 159: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration + 128, // 160: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial + 129, // 161: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput 9, // 162: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 269, // 163: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp - 269, // 164: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp - 269, // 165: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp + 271, // 163: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp + 271, // 164: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp + 271, // 165: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp 15, // 166: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 269, // 167: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp - 274, // 168: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 129, // 169: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 274, // 170: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 271, // 167: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp + 276, // 168: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 131, // 169: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 276, // 170: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 9, // 171: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 259, // 172: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - 269, // 173: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp - 129, // 174: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 274, // 175: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 129, // 176: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 274, // 177: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 261, // 172: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + 271, // 173: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp + 131, // 174: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 276, // 175: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 131, // 176: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 276, // 177: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 16, // 178: openshell.v1.DeleteProviderRefreshResponse.outcome:type_name -> openshell.v1.DeletionOutcome 10, // 179: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory - 125, // 180: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential - 277, // 181: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint - 278, // 182: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 130, // 183: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery - 260, // 184: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry - 139, // 185: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile - 139, // 186: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 274, // 187: openshell.v1.ImportProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 120, // 188: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 121, // 189: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 139, // 190: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 274, // 191: openshell.v1.UpdateProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 120, // 192: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem - 121, // 193: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 139, // 194: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile - 274, // 195: openshell.v1.LintProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 120, // 196: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 121, // 197: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 127, // 180: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential + 279, // 181: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 280, // 182: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 132, // 183: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery + 262, // 184: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry + 141, // 185: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile + 141, // 186: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 276, // 187: openshell.v1.ImportProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 122, // 188: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 123, // 189: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 141, // 190: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 276, // 191: openshell.v1.UpdateProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 122, // 192: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem + 123, // 193: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 141, // 194: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile + 276, // 195: openshell.v1.LintProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 122, // 196: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 123, // 197: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic 16, // 198: openshell.v1.DeleteProviderResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 274, // 199: openshell.v1.DeleteProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 199: openshell.v1.DeleteProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 16, // 200: openshell.v1.DeleteProviderProfileResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 152, // 201: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding - 261, // 202: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - 262, // 203: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - 263, // 204: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - 264, // 205: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + 154, // 201: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding + 263, // 202: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + 264, // 203: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + 265, // 204: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + 266, // 205: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry 4, // 206: openshell.v1.GetSandboxProviderEnvironmentResponse.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason - 273, // 207: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration - 274, // 208: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 271, // 209: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 279, // 210: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue - 158, // 211: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation - 265, // 212: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry - 159, // 213: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule - 160, // 214: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint - 161, // 215: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule - 163, // 216: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules - 164, // 217: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules - 165, // 218: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary - 280, // 219: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 278, // 220: openshell.v1.L7RuleTarget.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 281, // 221: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule - 162, // 222: openshell.v1.AddDenyRules.target:type_name -> openshell.v1.L7RuleTarget - 282, // 223: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule - 162, // 224: openshell.v1.AddAllowRules.target:type_name -> openshell.v1.L7RuleTarget - 266, // 225: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry - 274, // 226: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 176, // 227: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision - 274, // 228: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 176, // 229: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision + 275, // 207: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration + 276, // 208: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 273, // 209: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 281, // 210: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue + 160, // 211: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation + 267, // 212: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry + 161, // 213: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule + 162, // 214: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint + 163, // 215: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule + 165, // 216: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules + 166, // 217: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules + 167, // 218: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary + 282, // 219: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 280, // 220: openshell.v1.L7RuleTarget.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 283, // 221: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 164, // 222: openshell.v1.AddDenyRules.target:type_name -> openshell.v1.L7RuleTarget + 284, // 223: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule + 164, // 224: openshell.v1.AddAllowRules.target:type_name -> openshell.v1.L7RuleTarget + 268, // 225: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry + 276, // 226: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 178, // 227: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision + 276, // 228: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 178, // 229: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision 12, // 230: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus 11, // 231: openshell.v1.SandboxConfigurationAdmission.state:type_name -> openshell.v1.ConfigurationAdmissionState - 173, // 232: openshell.v1.ReportSandboxConfigurationRequest.admission:type_name -> openshell.v1.SandboxConfigurationAdmission + 175, // 232: openshell.v1.ReportSandboxConfigurationRequest.admission:type_name -> openshell.v1.SandboxConfigurationAdmission 12, // 233: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus - 269, // 234: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp - 269, // 235: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp - 271, // 236: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 267, // 237: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry - 274, // 238: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 269, // 239: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp - 109, // 240: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine - 109, // 241: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine - 183, // 242: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello - 186, // 243: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat - 197, // 244: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult - 198, // 245: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose - 184, // 246: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted - 185, // 247: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected - 187, // 248: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat - 192, // 249: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen - 198, // 250: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose - 273, // 251: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration - 193, // 252: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget - 194, // 253: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget - 195, // 254: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit - 269, // 255: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp - 269, // 256: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp - 199, // 257: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample - 201, // 258: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount - 280, // 259: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 269, // 260: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp - 269, // 261: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp - 269, // 262: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp - 269, // 263: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp - 271, // 264: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 271, // 265: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 274, // 266: openshell.v1.SubmitPolicyAnalysisRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 200, // 267: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary - 203, // 268: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk - 202, // 269: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary - 274, // 270: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 203, // 271: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk - 269, // 272: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp - 274, // 273: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 274: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 275: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 213, // 276: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval - 274, // 277: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 280, // 278: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 274, // 279: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 280: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 274, // 281: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 269, // 282: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp - 223, // 283: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry - 268, // 284: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry - 283, // 285: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 283, // 286: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 283, // 287: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace + 271, // 234: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp + 271, // 235: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp + 273, // 236: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 269, // 237: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry + 276, // 238: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 271, // 239: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp + 111, // 240: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine + 111, // 241: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine + 185, // 242: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello + 188, // 243: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat + 199, // 244: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult + 200, // 245: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose + 186, // 246: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted + 187, // 247: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected + 189, // 248: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat + 194, // 249: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen + 200, // 250: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose + 275, // 251: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration + 195, // 252: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget + 196, // 253: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget + 197, // 254: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit + 271, // 255: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp + 271, // 256: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp + 201, // 257: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample + 203, // 258: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount + 282, // 259: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 271, // 260: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp + 271, // 261: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp + 271, // 262: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp + 271, // 263: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp + 273, // 264: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 273, // 265: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 276, // 266: openshell.v1.SubmitPolicyAnalysisRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 202, // 267: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary + 205, // 268: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk + 204, // 269: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary + 276, // 270: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 205, // 271: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk + 271, // 272: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp + 276, // 273: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 274: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 275: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 215, // 276: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval + 276, // 277: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 282, // 278: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 276, // 279: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 280: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 281: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 271, // 282: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp + 225, // 283: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry + 270, // 284: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry + 285, // 285: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 285, // 286: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 285, // 287: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace 16, // 288: openshell.v1.DeleteWorkspaceResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 270, // 289: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 272, // 289: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta 14, // 290: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole - 274, // 291: openshell.v1.AddWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 276, // 291: openshell.v1.AddWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 14, // 292: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole - 233, // 293: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember - 274, // 294: openshell.v1.RemoveWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 235, // 293: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember + 276, // 294: openshell.v1.RemoveWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 16, // 295: openshell.v1.RemoveWorkspaceMemberResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 274, // 296: openshell.v1.ListWorkspaceMembersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 233, // 297: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember - 269, // 298: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp + 276, // 296: openshell.v1.ListWorkspaceMembersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 235, // 297: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember + 271, // 298: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp 17, // 299: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult - 241, // 300: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation + 243, // 300: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation 17, // 301: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult - 269, // 302: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp - 269, // 303: openshell.v1.SandboxProvisioning.configuration_change_time:type_name -> google.protobuf.Timestamp - 269, // 304: openshell.v1.SandboxProvisioning.first_rejection_time:type_name -> google.protobuf.Timestamp - 269, // 305: openshell.v1.SandboxProvisioning.deadline:type_name -> google.protobuf.Timestamp - 269, // 306: openshell.v1.SandboxProvisioning.timeout_time:type_name -> google.protobuf.Timestamp - 269, // 307: openshell.v1.SandboxProvisioning.cleanup_completed_time:type_name -> google.protobuf.Timestamp - 269, // 308: openshell.v1.SandboxProvisioning.cleanup_retry_time:type_name -> google.protobuf.Timestamp - 269, // 309: openshell.v1.SandboxProvisioning.attachment_change_time:type_name -> google.protobuf.Timestamp - 269, // 310: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 269, // 311: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 125, // 312: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 153, // 313: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding - 22, // 314: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest - 24, // 315: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest - 26, // 316: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest - 50, // 317: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest - 58, // 318: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest - 60, // 319: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest - 61, // 320: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest - 51, // 321: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest - 52, // 322: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest - 53, // 323: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest - 54, // 324: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest - 62, // 325: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest - 63, // 326: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest - 64, // 327: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest - 80, // 328: openshell.v1.OpenShell.GetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest - 65, // 329: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest - 66, // 330: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest - 67, // 331: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest - 85, // 332: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest - 87, // 333: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest - 88, // 334: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest - 89, // 335: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest - 91, // 336: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest - 95, // 337: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest - 97, // 338: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest - 103, // 339: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame - 104, // 340: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput - 111, // 341: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest - 112, // 342: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest - 113, // 343: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest - 118, // 344: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest - 119, // 345: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest - 142, // 346: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest - 144, // 347: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest - 146, // 348: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest - 114, // 349: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest - 131, // 350: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest - 133, // 351: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest - 135, // 352: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest - 137, // 353: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest - 115, // 354: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest - 149, // 355: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest - 284, // 356: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest - 285, // 357: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest - 157, // 358: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest - 167, // 359: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest - 169, // 360: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest - 171, // 361: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest - 242, // 362: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest - 82, // 363: openshell.v1.OpenShell.ReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest - 174, // 364: openshell.v1.OpenShell.ReportSandboxConfiguration:input_type -> openshell.v1.ReportSandboxConfigurationRequest - 151, // 365: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest - 155, // 366: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest - 177, // 367: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest - 178, // 368: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest - 181, // 369: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage - 188, // 370: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest - 190, // 371: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest - 196, // 372: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame - 107, // 373: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest - 205, // 374: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest - 207, // 375: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest - 209, // 376: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest - 211, // 377: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest - 214, // 378: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest - 216, // 379: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest - 218, // 380: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest - 220, // 381: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest - 222, // 382: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest - 18, // 383: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest - 20, // 384: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest - 225, // 385: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest - 227, // 386: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest - 229, // 387: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest - 231, // 388: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest - 234, // 389: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest - 236, // 390: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest - 238, // 391: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest - 23, // 392: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse - 25, // 393: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse - 27, // 394: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse - 68, // 395: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse - 59, // 396: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse - 68, // 397: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse - 69, // 398: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse - 55, // 399: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 55, // 400: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 56, // 401: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse - 57, // 402: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse - 70, // 403: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse - 71, // 404: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse - 72, // 405: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse - 81, // 406: openshell.v1.OpenShell.GetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse - 84, // 407: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse - 68, // 408: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse - 68, // 409: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse - 86, // 410: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse - 94, // 411: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse - 94, // 412: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse - 90, // 413: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse - 92, // 414: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse - 96, // 415: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse - 101, // 416: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent - 103, // 417: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame - 101, // 418: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent - 116, // 419: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse - 116, // 420: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse - 117, // 421: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse - 141, // 422: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse - 140, // 423: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse - 143, // 424: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse - 145, // 425: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse - 147, // 426: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse - 116, // 427: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse - 132, // 428: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse - 134, // 429: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse - 136, // 430: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse - 138, // 431: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse - 148, // 432: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse - 150, // 433: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse - 286, // 434: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse - 287, // 435: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse - 166, // 436: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse - 168, // 437: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse - 170, // 438: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse - 172, // 439: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse - 243, // 440: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse - 83, // 441: openshell.v1.OpenShell.ReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse - 175, // 442: openshell.v1.OpenShell.ReportSandboxConfiguration:output_type -> openshell.v1.ReportSandboxConfigurationResponse - 154, // 443: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse - 156, // 444: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse - 180, // 445: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse - 179, // 446: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse - 182, // 447: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage - 189, // 448: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse - 191, // 449: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse - 196, // 450: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame - 108, // 451: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent - 206, // 452: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse - 208, // 453: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse - 210, // 454: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse - 212, // 455: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse - 215, // 456: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse - 217, // 457: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse - 219, // 458: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse - 221, // 459: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse - 224, // 460: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse - 19, // 461: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse - 21, // 462: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse - 226, // 463: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse - 228, // 464: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse - 230, // 465: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse - 232, // 466: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse - 235, // 467: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse - 237, // 468: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse - 239, // 469: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse - 392, // [392:470] is the sub-list for method output_type - 314, // [314:392] is the sub-list for method input_type + 271, // 302: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp + 271, // 303: openshell.v1.SandboxProvisioning.configuration_change_time:type_name -> google.protobuf.Timestamp + 271, // 304: openshell.v1.SandboxProvisioning.first_rejection_time:type_name -> google.protobuf.Timestamp + 271, // 305: openshell.v1.SandboxProvisioning.deadline:type_name -> google.protobuf.Timestamp + 271, // 306: openshell.v1.SandboxProvisioning.timeout_time:type_name -> google.protobuf.Timestamp + 271, // 307: openshell.v1.SandboxProvisioning.cleanup_completed_time:type_name -> google.protobuf.Timestamp + 271, // 308: openshell.v1.SandboxProvisioning.cleanup_retry_time:type_name -> google.protobuf.Timestamp + 271, // 309: openshell.v1.SandboxProvisioning.attachment_change_time:type_name -> google.protobuf.Timestamp + 271, // 310: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 271, // 311: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 127, // 312: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 155, // 313: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding + 24, // 314: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest + 26, // 315: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest + 28, // 316: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest + 52, // 317: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest + 60, // 318: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest + 62, // 319: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest + 63, // 320: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest + 53, // 321: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest + 54, // 322: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest + 55, // 323: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest + 56, // 324: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest + 64, // 325: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest + 65, // 326: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest + 66, // 327: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest + 82, // 328: openshell.v1.OpenShell.GetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest + 67, // 329: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest + 68, // 330: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest + 69, // 331: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest + 87, // 332: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest + 89, // 333: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest + 90, // 334: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest + 91, // 335: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest + 93, // 336: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest + 97, // 337: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest + 99, // 338: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest + 105, // 339: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame + 106, // 340: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput + 113, // 341: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest + 114, // 342: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest + 115, // 343: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest + 120, // 344: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest + 121, // 345: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest + 144, // 346: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest + 146, // 347: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest + 148, // 348: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest + 116, // 349: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest + 133, // 350: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest + 135, // 351: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest + 137, // 352: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest + 139, // 353: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest + 117, // 354: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest + 151, // 355: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest + 286, // 356: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest + 287, // 357: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest + 159, // 358: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest + 169, // 359: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest + 171, // 360: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest + 173, // 361: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest + 244, // 362: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest + 84, // 363: openshell.v1.OpenShell.ReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest + 176, // 364: openshell.v1.OpenShell.ReportSandboxConfiguration:input_type -> openshell.v1.ReportSandboxConfigurationRequest + 153, // 365: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest + 157, // 366: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest + 179, // 367: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest + 180, // 368: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest + 183, // 369: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage + 190, // 370: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest + 192, // 371: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest + 198, // 372: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame + 109, // 373: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest + 207, // 374: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest + 209, // 375: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest + 211, // 376: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest + 213, // 377: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest + 216, // 378: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest + 218, // 379: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest + 220, // 380: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest + 222, // 381: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest + 224, // 382: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest + 18, // 383: openshell.v1.OpenShell.RegisterSupervisor:input_type -> openshell.v1.RegisterSupervisorRequest + 20, // 384: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest + 22, // 385: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest + 227, // 386: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest + 229, // 387: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest + 231, // 388: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest + 233, // 389: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest + 236, // 390: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest + 238, // 391: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest + 240, // 392: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest + 25, // 393: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse + 27, // 394: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse + 29, // 395: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse + 70, // 396: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse + 61, // 397: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse + 70, // 398: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse + 71, // 399: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse + 57, // 400: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 57, // 401: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 58, // 402: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse + 59, // 403: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse + 72, // 404: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse + 73, // 405: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse + 74, // 406: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse + 83, // 407: openshell.v1.OpenShell.GetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse + 86, // 408: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse + 70, // 409: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse + 70, // 410: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse + 88, // 411: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse + 96, // 412: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse + 96, // 413: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse + 92, // 414: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse + 94, // 415: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse + 98, // 416: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse + 103, // 417: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent + 105, // 418: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame + 103, // 419: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent + 118, // 420: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse + 118, // 421: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse + 119, // 422: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse + 143, // 423: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse + 142, // 424: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse + 145, // 425: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse + 147, // 426: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse + 149, // 427: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse + 118, // 428: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse + 134, // 429: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse + 136, // 430: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse + 138, // 431: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse + 140, // 432: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse + 150, // 433: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse + 152, // 434: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse + 288, // 435: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse + 289, // 436: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse + 168, // 437: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse + 170, // 438: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse + 172, // 439: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse + 174, // 440: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse + 245, // 441: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse + 85, // 442: openshell.v1.OpenShell.ReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse + 177, // 443: openshell.v1.OpenShell.ReportSandboxConfiguration:output_type -> openshell.v1.ReportSandboxConfigurationResponse + 156, // 444: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse + 158, // 445: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse + 182, // 446: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse + 181, // 447: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse + 184, // 448: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage + 191, // 449: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse + 193, // 450: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse + 198, // 451: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame + 110, // 452: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent + 208, // 453: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse + 210, // 454: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse + 212, // 455: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse + 214, // 456: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse + 217, // 457: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse + 219, // 458: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse + 221, // 459: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse + 223, // 460: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse + 226, // 461: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse + 19, // 462: openshell.v1.OpenShell.RegisterSupervisor:output_type -> openshell.v1.RegisterSupervisorResponse + 21, // 463: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse + 23, // 464: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse + 228, // 465: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse + 230, // 466: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse + 232, // 467: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse + 234, // 468: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse + 237, // 469: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse + 239, // 470: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse + 241, // 471: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse + 393, // [393:472] is the sub-list for method output_type + 314, // [314:393] is the sub-list for method input_type 314, // [314:314] is the sub-list for extension type_name 314, // [314:314] is the sub-list for extension extendee 0, // [0:314] is the sub-list for field type_name @@ -19700,40 +19828,40 @@ func file_openshell_proto_init() { if File_openshell_proto != nil { return } - 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[22].OneofWrappers = []any{} + file_openshell_proto_msgTypes[23].OneofWrappers = []any{} + file_openshell_proto_msgTypes[31].OneofWrappers = []any{} + file_openshell_proto_msgTypes[58].OneofWrappers = []any{ (*ConfigSnapshotRevision_SandboxConfig)(nil), (*ConfigSnapshotRevision_ProviderEnvironment)(nil), (*ConfigSnapshotRevision_ProviderTarget)(nil), } - file_openshell_proto_msgTypes[83].OneofWrappers = []any{ + file_openshell_proto_msgTypes[85].OneofWrappers = []any{ (*ExecSandboxEvent_Stdout)(nil), (*ExecSandboxEvent_Stderr)(nil), (*ExecSandboxEvent_Exit)(nil), } - file_openshell_proto_msgTypes[84].OneofWrappers = []any{ + file_openshell_proto_msgTypes[86].OneofWrappers = []any{ (*TcpForwardInit_Ssh)(nil), (*TcpForwardInit_Tcp)(nil), } - file_openshell_proto_msgTypes[85].OneofWrappers = []any{ + file_openshell_proto_msgTypes[87].OneofWrappers = []any{ (*TcpForwardFrame_Init)(nil), (*TcpForwardFrame_Data)(nil), } - file_openshell_proto_msgTypes[86].OneofWrappers = []any{ + file_openshell_proto_msgTypes[88].OneofWrappers = []any{ (*ExecSandboxInput_Start)(nil), (*ExecSandboxInput_Stdin)(nil), (*ExecSandboxInput_Resize)(nil), } - file_openshell_proto_msgTypes[90].OneofWrappers = []any{ + file_openshell_proto_msgTypes[92].OneofWrappers = []any{ (*SandboxStreamEvent_Sandbox)(nil), (*SandboxStreamEvent_Log)(nil), (*SandboxStreamEvent_Event)(nil), (*SandboxStreamEvent_Warning)(nil), (*SandboxStreamEvent_DraftPolicyUpdate)(nil), } - file_openshell_proto_msgTypes[140].OneofWrappers = []any{ + file_openshell_proto_msgTypes[142].OneofWrappers = []any{ (*PolicyMergeOperation_AddRule)(nil), (*PolicyMergeOperation_RemoveEndpoint)(nil), (*PolicyMergeOperation_RemoveRule)(nil), @@ -19741,25 +19869,25 @@ func file_openshell_proto_init() { (*PolicyMergeOperation_AddAllowRules)(nil), (*PolicyMergeOperation_RemoveBinary)(nil), } - file_openshell_proto_msgTypes[144].OneofWrappers = []any{} - file_openshell_proto_msgTypes[163].OneofWrappers = []any{ + file_openshell_proto_msgTypes[146].OneofWrappers = []any{} + file_openshell_proto_msgTypes[165].OneofWrappers = []any{ (*SupervisorMessage_Hello)(nil), (*SupervisorMessage_Heartbeat)(nil), (*SupervisorMessage_RelayOpenResult)(nil), (*SupervisorMessage_RelayClose)(nil), } - file_openshell_proto_msgTypes[164].OneofWrappers = []any{ + file_openshell_proto_msgTypes[166].OneofWrappers = []any{ (*GatewayMessage_SessionAccepted)(nil), (*GatewayMessage_SessionRejected)(nil), (*GatewayMessage_Heartbeat)(nil), (*GatewayMessage_RelayOpen)(nil), (*GatewayMessage_RelayClose)(nil), } - file_openshell_proto_msgTypes[174].OneofWrappers = []any{ + file_openshell_proto_msgTypes[176].OneofWrappers = []any{ (*RelayOpen_Ssh)(nil), (*RelayOpen_Tcp)(nil), } - file_openshell_proto_msgTypes[178].OneofWrappers = []any{ + file_openshell_proto_msgTypes[180].OneofWrappers = []any{ (*RelayFrame_Init)(nil), (*RelayFrame_Data)(nil), } @@ -19769,7 +19897,7 @@ func file_openshell_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_openshell_proto_rawDesc), len(file_openshell_proto_rawDesc)), NumEnums: 18, - NumMessages: 251, + NumMessages: 253, NumExtensions: 0, NumServices: 1, }, diff --git a/sdk/go/proto/openshellv1/openshell_grpc.pb.go b/sdk/go/proto/openshellv1/openshell_grpc.pb.go index 0609450c89..b094137fef 100644 --- a/sdk/go/proto/openshellv1/openshell_grpc.pb.go +++ b/sdk/go/proto/openshellv1/openshell_grpc.pb.go @@ -92,6 +92,7 @@ const ( OpenShell_UndoDraftChunk_FullMethodName = "/openshell.v1.OpenShell/UndoDraftChunk" OpenShell_ClearDraftChunks_FullMethodName = "/openshell.v1.OpenShell/ClearDraftChunks" OpenShell_GetDraftHistory_FullMethodName = "/openshell.v1.OpenShell/GetDraftHistory" + OpenShell_RegisterSupervisor_FullMethodName = "/openshell.v1.OpenShell/RegisterSupervisor" OpenShell_IssueSandboxToken_FullMethodName = "/openshell.v1.OpenShell/IssueSandboxToken" OpenShell_RefreshSandboxToken_FullMethodName = "/openshell.v1.OpenShell/RefreshSandboxToken" OpenShell_CreateWorkspace_FullMethodName = "/openshell.v1.OpenShell/CreateWorkspace" @@ -292,6 +293,8 @@ type OpenShellClient interface { ClearDraftChunks(ctx context.Context, in *ClearDraftChunksRequest, opts ...grpc.CallOption) (*ClearDraftChunksResponse, error) // Get decision history for a sandbox's draft policy. GetDraftHistory(ctx context.Context, in *GetDraftHistoryRequest, opts ...grpc.CallOption) (*GetDraftHistoryResponse, error) + // Wait for the driver-verified proxy's durable assignment before activation. + RegisterSupervisor(ctx context.Context, in *RegisterSupervisorRequest, opts ...grpc.CallOption) (*RegisterSupervisorResponse, error) // Exchange a sandbox-bootstrap credential (e.g. a Kubernetes projected // ServiceAccount token) for a gateway-minted JWT bound to the calling // sandbox's UUID. Used by the Kubernetes driver path; singleplayer @@ -1052,6 +1055,16 @@ func (c *openShellClient) GetDraftHistory(ctx context.Context, in *GetDraftHisto return out, nil } +func (c *openShellClient) RegisterSupervisor(ctx context.Context, in *RegisterSupervisorRequest, opts ...grpc.CallOption) (*RegisterSupervisorResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RegisterSupervisorResponse) + err := c.cc.Invoke(ctx, OpenShell_RegisterSupervisor_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *openShellClient) IssueSandboxToken(ctx context.Context, in *IssueSandboxTokenRequest, opts ...grpc.CallOption) (*IssueSandboxTokenResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(IssueSandboxTokenResponse) @@ -1331,6 +1344,8 @@ type OpenShellServer interface { ClearDraftChunks(context.Context, *ClearDraftChunksRequest) (*ClearDraftChunksResponse, error) // Get decision history for a sandbox's draft policy. GetDraftHistory(context.Context, *GetDraftHistoryRequest) (*GetDraftHistoryResponse, error) + // Wait for the driver-verified proxy's durable assignment before activation. + RegisterSupervisor(context.Context, *RegisterSupervisorRequest) (*RegisterSupervisorResponse, error) // Exchange a sandbox-bootstrap credential (e.g. a Kubernetes projected // ServiceAccount token) for a gateway-minted JWT bound to the calling // sandbox's UUID. Used by the Kubernetes driver path; singleplayer @@ -1575,6 +1590,9 @@ func (UnimplementedOpenShellServer) ClearDraftChunks(context.Context, *ClearDraf func (UnimplementedOpenShellServer) GetDraftHistory(context.Context, *GetDraftHistoryRequest) (*GetDraftHistoryResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetDraftHistory not implemented") } +func (UnimplementedOpenShellServer) RegisterSupervisor(context.Context, *RegisterSupervisorRequest) (*RegisterSupervisorResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RegisterSupervisor not implemented") +} func (UnimplementedOpenShellServer) IssueSandboxToken(context.Context, *IssueSandboxTokenRequest) (*IssueSandboxTokenResponse, error) { return nil, status.Error(codes.Unimplemented, "method IssueSandboxToken not implemented") } @@ -2796,6 +2814,24 @@ func _OpenShell_GetDraftHistory_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _OpenShell_RegisterSupervisor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RegisterSupervisorRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).RegisterSupervisor(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_RegisterSupervisor_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).RegisterSupervisor(ctx, req.(*RegisterSupervisorRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _OpenShell_IssueSandboxToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(IssueSandboxTokenRequest) if err := dec(in); err != nil { @@ -3213,6 +3249,10 @@ var OpenShell_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetDraftHistory", Handler: _OpenShell_GetDraftHistory_Handler, }, + { + MethodName: "RegisterSupervisor", + Handler: _OpenShell_RegisterSupervisor_Handler, + }, { MethodName: "IssueSandboxToken", Handler: _OpenShell_IssueSandboxToken_Handler, diff --git a/skills/debug-openshell-cluster/SKILL.md b/skills/debug-openshell-cluster/SKILL.md index 7f41477a19..c7d2a00104 100644 --- a/skills/debug-openshell-cluster/SKILL.md +++ b/skills/debug-openshell-cluster/SKILL.md @@ -90,6 +90,30 @@ Use gateway metadata, deployment values, or the user's setup notes to identify t ### Step 3: Check Gateway Startup Dependencies +For missing Kubernetes warm capacity, check template startup hints +(`startup.ready_within` and a positive `startup.max_burst`) and whether the +deployment explicitly disables `server.drivers.kubernetes.warmPool.enabled` +or `[openshell.drivers.kubernetes.warm_pool].enabled`. Pooling is enabled by +default, but templates without these hints request no spare pairs. Inspect +gateway logs for warm pool election or preparation failures and check the +configured inventory cap. + +Warm-pool Pod names retain their prepared identities after assignment. Use +`kubectl -n get pods -L openshell.ai/sandbox-name,openshell.ai/boundary-role` +to display logical sandbox names, or select `openshell.ai/sandbox-id=` to +find both Pods across potentially duplicate names in different workspaces. +These labels and the `openshell.ai/sandbox-id` annotation are repaired +asynchronously from the Sandbox resource; the `openshell.ai/boundary-pair` label +continues to identify the physical pair. When SPIFFE provider token grants fail +after warm assignment, check that the supervisor Pod's sandbox-ID annotation +matches the logical sandbox ID used by the SPIRE identity mapping. +Use `-L openshell.ai/template-name` to identify idle warm capacity by template; +`openshell.ai/template-id` distinguishes templates across workspaces. Both labels +remain on claimed pairs, so a template selector includes assigned Pods too. +Local assignment notifications avoid the registration polling delay, but a +registration handled by another gateway replica can still wait up to one poll +interval before noticing the claim. + Before debugging the compute platform, inspect gateway logs for failures in dependencies initialized before the listener becomes ready. For out-of-tree compute drivers, confirm the selected driver name and socket agree across CLI flags or `gateway.toml`, and that the operator-owned driver is running before the gateway starts: @@ -204,6 +228,21 @@ fails discovery within its control-request deadline. Permanent gateway errors and exhausted transient retries terminate startup; inspect those errors as connectivity, authorization, or lifecycle failures. +For Kubernetes registration-based startup, distinguish waiting for assignment +from failure to attach the workload. Check gateway `RegisterSupervisor` errors, +projected-token audience and TokenReview access, and live Sandbox/Pod ownership. +The gateway also needs `get` on Secrets in sandbox namespaces to validate the +prepared proxy descriptor. Repeated `Unavailable` registration responses with +`failed to read proxy registration bootstrap Secret` in the gateway log can +indicate missing RBAC permission; check the API error without dumping the Secret. +Stop and rollback cleanup also require Secret `list` and `delete` permissions. +Supervisor deletion allows the Pod termination grace period plus cleanup time; +capture terminating Pod status and logs before test cleanup removes them. +The proxy bootstrap Secret contains transport and proxy-CA material; an absent +`auth.json` is expected. Operational credentials arrive through registration. +Use the [gateway authentication reference](https://docs.nvidia.com/openshell/latest/reference/gateway-auth.md) +for the authentication flow. Do not print Secret contents or projected tokens. + ### Step 4: Check Docker-Backed Gateways ```bash @@ -595,6 +634,40 @@ If SSH relay connections fail, verify the workspace NetworkPolicy selects the gateway's actual `app.kubernetes.io/name` and `app.kubernetes.io/instance` labels. +For optional warm-pool preparation, inspect Sandbox labels +`openshell.ai/warm-pool` and `openshell.ai/warm-pool-state`. A `ready` spare has +no logical sandbox ID and its proxy is still waiting in `RegisterSupervisor`; +the proxy Pod's ordinary readiness probe remains false until activation. Check +the workload's `boundary-health` probe and the +`openshell.ai/warm-proxy-registered-at-ms` annotation for standby readiness. +Controller election needs Lease get/create/update permissions in the driver's +configured namespace. Repeated preparation failures appear in gateway logs; +template removal or pool disablement should retire only unassigned pairs. +In operator mode, `skipping unavailable warm pool workspace` means that template's +namespace is outside the allowlist; other eligible pools continue refreshing. +An interrupted preparation can resume after its workload scheduling gate is gone +if the recorded resource identities and existing bootstrap journal still match. +The preparation Secret journal contains TLS private keys: inspect metadata, +never print its data. Assignment removes the pool labels and adds the logical +sandbox ID to the parent. The `openshell.ai/warm-pair-id` annotation remains the +physical identity used by companion resources, including after stop/start. +For an allocation stuck in Provisioning, inspect the logical sandbox's +`internal.openshell.ai/warm-pair-pending` annotation and gateway claim-recovery logs. +An uncertain claim deliberately keeps the same candidate; do not manually clear +its annotations or return an assigned pair to the pool. +Target reservations survive gateway restart and have no TTL. Reservation +conflicts immediately try another candidate without retiring the other sandbox's +pair; definite claim rejection uses bounded retries before cold fallback. +Do not remove allocation reservations to force progress on an uncertain claim. +Completed and failed main-process results finalize pending claim recovery. Explicit +restart clears that pending marker with its new attempt while retaining the assigned +pair and reservation; recovery must not select another pair for that restart. +After `ProvisioningTimedOut`, recovery retires the pending candidate instead of +retrying allocation. Cleanup remains pending until candidate retirement and +compute reclamation succeed. Leftover allocation annotations require cleanup +even if an older gateway recorded completion; explicitly start the sandbox only +after cleanup finishes. + Check the configured sandbox service account when TokenReview bootstrap or sandbox registration fails. Helm creates a dedicated sandbox service account by default and writes it to `[openshell.drivers.kubernetes].service_account_name`;