diff --git a/.agents/skills/tui-development/SKILL.md b/.agents/skills/tui-development/SKILL.md index 4eac50fefc..0de6246c9f 100644 --- a/.agents/skills/tui-development/SKILL.md +++ b/.agents/skills/tui-development/SKILL.md @@ -484,8 +484,12 @@ use openshell_core::proto::{ let req = openshell_core::proto::DeleteSandboxRequest { name: sandbox_name, workspace_scope: Some(workspace_selector(workspace)), + allow_missing: true, }; ``` +- Delete responses carry `DeletionOutcome`: distinguish `Accepted` (cleanup + pending), `Completed`, and `AlreadyAbsent`. Treat unspecified or unknown + outcomes as unconfirmed, not completed. - `WatchSandboxRequest` has extra fields beyond what you might need — always use `..Default::default()`: ```rust let req = openshell_core::proto::WatchSandboxRequest { diff --git a/architecture/gateway.md b/architecture/gateway.md index 48e3ccc9f6..5e04f377fa 100644 --- a/architecture/gateway.md +++ b/architecture/gateway.md @@ -59,8 +59,8 @@ write conflicts attach `google.rpc.ErrorInfo` with a stable reason and current version when available. `google.rpc.RetryInfo` expresses a minimum retry delay; it does not establish that a mutation is safe to repeat. SDKs retain the original transport status, metadata, and unknown details alongside decoded fields. -Python cleanup inspects the original gRPC call beneath a typed error wrapper, -preserving missing-resource handling without suppressing other failures. +SDK deletion waits recognize missing-resource status through typed error wrappers +without suppressing other failures. The gateway listens on one service port and multiplexes gRPC and HTTP traffic. The default local single-user deployment mode is mTLS user authentication: @@ -376,6 +376,23 @@ than extending the frozen message. The descriptor-derived test inventories the complete message and enum closure of the encoded durable roots and its intersection with the public RPC closure. The tables here record the reviewed roots and classifications. +Public delete, membership-removal, and SSH-revocation responses use +`DeletionOutcome`, not a transport-success boolean. `COMPLETED` establishes +logical gateway deletion or revocation; it does not guarantee that downstream +platform garbage collection has finished. Sandbox deletion returns `ACCEPTED` +while its captured object ID remains in the store, and returns that ID so callers +can distinguish the original sandbox from a same-name replacement. Identity-aware +SDK deletion waits complete on absence or a different observed ID; name-only waits +continue until the name is absent. The existing owned deletion worker continues +after request cancellation. + +Missing targets return `NOT_FOUND` unless `allow_missing` explicitly requests +`ALREADY_ABSENT`. Authorization, parent resolution, preconditions, and backend +failures remain errors. Already-revoked sessions complete without another write +after current authorization. The removed response booleans are reserved by name +and number; this coordinated pre-1.0 API change does not alter durable schemas. +It does not add request deduplication or identity preconditions for later retries. + | Dual-purpose encoded root | Current decision | |---|---| | `Sandbox` | Defer a storage twin; govern its complete dependency closure as durable. | diff --git a/crates/openshell-cli/src/commands/provider.rs b/crates/openshell-cli/src/commands/provider.rs index 1005bfd256..414b624c92 100644 --- a/crates/openshell-cli/src/commands/provider.rs +++ b/crates/openshell-cli/src/commands/provider.rs @@ -676,6 +676,7 @@ async fn rollback_provider_create_after_gcloud_adc_failure( ) -> Result<()> { match client .delete_provider(DeleteProviderRequest { + allow_missing: true, name: provider_name.to_string(), workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) @@ -1713,6 +1714,7 @@ pub async fn provider_profile_delete( for id in ids { let response = match client .delete_provider_profile(DeleteProviderProfileRequest { + allow_missing: true, id: id.clone(), workspace: workspace.to_string(), }) @@ -1728,7 +1730,7 @@ pub async fn provider_profile_delete( continue; } }; - if response.deleted { + if crate::run::deletion_completed(response.outcome)? { println!("{} Deleted provider profile {id}", "✓".green().bold()); } else { println!("{} Provider profile {id} not found", "!".yellow()); @@ -1898,6 +1900,7 @@ pub async fn provider_refresh_delete( let mut client = grpc_client(server, tls).await?; let response = client .delete_provider_refresh(DeleteProviderRefreshRequest { + allow_missing: true, provider: name.to_string(), credential_key: credential_key.to_string(), workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), @@ -1906,7 +1909,7 @@ pub async fn provider_refresh_delete( .into_diagnostic()? .into_inner(); - if response.deleted { + if crate::run::deletion_completed(response.outcome)? { println!( "{} Deleted refresh config for {} {}", "✓".green().bold(), @@ -2396,6 +2399,7 @@ pub async fn provider_delete( for name in names { let response = match client .delete_provider(DeleteProviderRequest { + allow_missing: true, name: name.clone(), workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) @@ -2411,7 +2415,7 @@ pub async fn provider_delete( continue; } }; - if response.into_inner().deleted { + if crate::run::deletion_completed(response.into_inner().outcome)? { println!("{} Deleted provider {name}", "✓".green().bold()); } else { println!("{} Provider {name} not found", "!".yellow()); diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index 0dc97c1c28..9dbbe763b5 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -46,15 +46,16 @@ use openshell_core::proto::{ ApproveAllDraftChunksRequest, ApproveDraftChunkRequest, BeginRootfsTarStagingRequest, ClearDraftChunksRequest, CreateSandboxRequest, CreateSandboxTemplateRequest, CreateSshSessionRequest, DeleteSandboxRequest, DeleteSandboxTemplateRequest, - DeleteServiceRequest, EndpointResult, EndpointStatus, ExecSandboxRequest, ExposeServiceRequest, - GetCurrentUserRequest, GetDraftHistoryRequest, GetDraftPolicyRequest, GetGatewayConfigRequest, - GetSandboxConfigRequest, GetSandboxConfigResponse, GetSandboxLogsRequest, - GetSandboxPolicyStatusRequest, GetSandboxRequest, GetSandboxTemplateRequest, GetServiceRequest, - GpuResourceRequirements, ListSandboxPoliciesRequest, ListSandboxTemplatesRequest, - ListSandboxesRequest, ListServicesRequest, PolicySource, PolicyStatus, RejectDraftChunkRequest, - ResourceRequirements, RevokeSshSessionRequest, Sandbox, SandboxCondition, SandboxPhase, - SandboxPolicy, SandboxResources, SandboxServiceLevel, SandboxSpec, SandboxStartup, - SandboxTemplate, SandboxWorkloadConfig, SandboxWorkloadTemplate, SandboxWorkloadTemplateSpec, + DeleteServiceRequest, DeletionOutcome, EndpointResult, EndpointStatus, ExecSandboxRequest, + ExposeServiceRequest, GetCurrentUserRequest, GetDraftHistoryRequest, GetDraftPolicyRequest, + GetGatewayConfigRequest, GetSandboxConfigRequest, GetSandboxConfigResponse, + GetSandboxLogsRequest, GetSandboxPolicyStatusRequest, GetSandboxRequest, + GetSandboxTemplateRequest, GetServiceRequest, GpuResourceRequirements, + ListSandboxPoliciesRequest, ListSandboxTemplatesRequest, ListSandboxesRequest, + ListServicesRequest, PolicySource, PolicyStatus, RejectDraftChunkRequest, ResourceRequirements, + RevokeSshSessionRequest, Sandbox, SandboxCondition, SandboxPhase, SandboxPolicy, + SandboxResources, SandboxServiceLevel, SandboxSpec, SandboxStartup, SandboxTemplate, + SandboxWorkloadConfig, SandboxWorkloadTemplate, SandboxWorkloadTemplateSpec, ServiceEndpointResponse, SettingScope, StartSandboxRequest, StopSandboxRequest, TcpForwardFrame, TcpForwardInit, TcpRelayTarget, UpdateConfigRequest, WatchSandboxRequest, exec_sandbox_event, tcp_forward_init, @@ -1940,7 +1941,7 @@ pub async fn service_forward_tcp( } } let _ = client - .revoke_ssh_session(RevokeSshSessionRequest { token }) + .revoke_ssh_session(RevokeSshSessionRequest { allow_missing: true, token }) .await; }); } @@ -2850,6 +2851,17 @@ pub async fn sandbox_template_list( Ok(()) } +pub(crate) fn deletion_completed(outcome: i32) -> Result { + use openshell_core::proto::DeletionOutcome; + match DeletionOutcome::try_from(outcome) { + Ok(DeletionOutcome::Completed) => Ok(true), + Ok(DeletionOutcome::AlreadyAbsent) => Ok(false), + _ => Err(miette!( + "gateway returned an unsupported deletion outcome: {outcome}" + )), + } +} + pub async fn sandbox_template_delete( server: &str, names: &[String], @@ -2860,12 +2872,13 @@ pub async fn sandbox_template_delete( for name in names { let response = client .delete_sandbox_template(DeleteSandboxTemplateRequest { + allow_missing: true, name: name.clone(), workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await .into_diagnostic()?; - if response.into_inner().deleted { + if deletion_completed(response.into_inner().outcome)? { println!("{} Deleted sandbox template {name}", "✓".green().bold()); } else { println!("Sandbox template {name} not found."); @@ -3287,17 +3300,13 @@ pub async fn sandbox_delete( let response = match client .delete_sandbox(DeleteSandboxRequest { + allow_missing: true, name: name.clone(), workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await { Ok(response) => response, - Err(status) if status.code() == Code::NotFound => { - clear_last_sandbox_if_matches(gateway, workspace, name); - println!("{} Sandbox {name} already deleted", "✓".green().bold()); - continue; - } Err(status) => { eprintln!( "{} Failed to delete sandbox {name}: {status}", @@ -3308,13 +3317,25 @@ pub async fn sandbox_delete( } }; - let deleted = response.into_inner().deleted; - if deleted { - clear_last_sandbox_if_matches(gateway, workspace, name); - println!("{} Deleted sandbox {name}", "✓".green().bold()); - } else { - println!("{} Sandbox {name} not found", "!".yellow()); + match response.into_inner().outcome() { + DeletionOutcome::Completed => println!("{} Deleted sandbox {name}", "✓".green().bold()), + DeletionOutcome::Accepted => println!( + "{} Sandbox {name} deletion accepted; cleanup is pending", + "✓".green().bold() + ), + DeletionOutcome::AlreadyAbsent => { + println!("{} Sandbox {name} already deleted", "✓".green().bold()); + } + DeletionOutcome::Unspecified => { + eprintln!( + "{} Unsupported deletion outcome for sandbox {name}", + "!".red().bold() + ); + failures.push(name.clone()); + continue; + } } + clear_last_sandbox_if_matches(gateway, workspace, name); } aggregate_delete_failures("sandbox", &failures) @@ -3586,6 +3607,7 @@ pub async fn service_delete( let mut client = grpc_client(server, tls).await?; let response = client .delete_service(DeleteServiceRequest { + allow_missing: false, sandbox: sandbox.to_string(), service: service.to_string(), workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), @@ -3594,7 +3616,7 @@ pub async fn service_delete( .map_err(|status| service_status_error("delete service", "sandbox:write", status))? .into_inner(); - if !response.deleted { + if !deletion_completed(response.outcome)? { return Err(miette!("delete service failed: service endpoint not found")); } @@ -3958,10 +3980,13 @@ pub async fn workspace_delete(server: &str, names: &[String], tls: &TlsOptions) let mut client = grpc_client(server, tls).await?; for name in names { let response = client - .delete_workspace(DeleteWorkspaceRequest { name: name.clone() }) + .delete_workspace(DeleteWorkspaceRequest { + allow_missing: false, + name: name.clone(), + }) .await .into_diagnostic()?; - if response.into_inner().deleted { + if deletion_completed(response.into_inner().outcome)? { println!("{} Deleted workspace {name}", "✓".green().bold()); } else { println!("{} Workspace {name} not found", "!".yellow()); @@ -4027,13 +4052,14 @@ pub async fn workspace_member_remove( let mut client = grpc_client(server, tls).await?; let response = client .remove_workspace_member(RemoveWorkspaceMemberRequest { + allow_missing: true, workspace: workspace.to_string(), principal_subject: subject.to_string(), }) .await .into_diagnostic()?; - if response.into_inner().removed { + if deletion_completed(response.into_inner().outcome)? { println!( "{} Removed {} from workspace {}", "✓".green().bold(), diff --git a/crates/openshell-cli/tests/ensure_providers_integration.rs b/crates/openshell-cli/tests/ensure_providers_integration.rs index ca36ed6554..017a165ff3 100644 --- a/crates/openshell-cli/tests/ensure_providers_integration.rs +++ b/crates/openshell-cli/tests/ensure_providers_integration.rs @@ -195,7 +195,10 @@ impl OpenShell for TestOpenShell { &self, _request: tonic::Request, ) -> Result, Status> { - Ok(Response::new(DeleteSandboxResponse { deleted: true })) + Ok(Response::new(DeleteSandboxResponse { + sandbox_id: String::new(), + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + })) } async fn get_sandbox_config( @@ -496,7 +499,13 @@ impl OpenShell for TestOpenShell { ) -> Result, Status> { let name = request.into_inner().name; let deleted = self.state.providers.lock().await.remove(&name).is_some(); - Ok(Response::new(DeleteProviderResponse { deleted })) + Ok(Response::new(DeleteProviderResponse { + outcome: if deleted { + openshell_core::proto::DeletionOutcome::Completed.into() + } else { + openshell_core::proto::DeletionOutcome::AlreadyAbsent.into() + }, + })) } type WatchSandboxStream = diff --git a/crates/openshell-cli/tests/mtls_integration.rs b/crates/openshell-cli/tests/mtls_integration.rs index f916d704bd..fa732c3a03 100644 --- a/crates/openshell-cli/tests/mtls_integration.rs +++ b/crates/openshell-cli/tests/mtls_integration.rs @@ -161,7 +161,10 @@ impl OpenShell for TestOpenShell { _request: tonic::Request, ) -> Result, Status> { Ok(Response::new( - openshell_core::proto::DeleteSandboxResponse { deleted: true }, + openshell_core::proto::DeleteSandboxResponse { + sandbox_id: String::new(), + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + }, )) } diff --git a/crates/openshell-cli/tests/provider_commands_integration.rs b/crates/openshell-cli/tests/provider_commands_integration.rs index 12f9be0bd7..2c9b9ba005 100644 --- a/crates/openshell-cli/tests/provider_commands_integration.rs +++ b/crates/openshell-cli/tests/provider_commands_integration.rs @@ -331,7 +331,10 @@ impl OpenShell for TestOpenShell { &self, _request: tonic::Request, ) -> Result, Status> { - Ok(Response::new(DeleteSandboxResponse { deleted: true })) + Ok(Response::new(DeleteSandboxResponse { + sandbox_id: String::new(), + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + })) } async fn get_sandbox_config( @@ -887,7 +890,13 @@ impl OpenShell for TestOpenShell { .await .remove(&(request.provider, request.credential_key)) .is_some(); - Ok(Response::new(DeleteProviderRefreshResponse { deleted })) + Ok(Response::new(DeleteProviderRefreshResponse { + outcome: if deleted { + openshell_core::proto::DeletionOutcome::Completed.into() + } else { + openshell_core::proto::DeletionOutcome::AlreadyAbsent.into() + }, + })) } async fn delete_provider( @@ -905,7 +914,13 @@ impl OpenShell for TestOpenShell { return Err(Status::internal(message)); } let deleted = self.state.providers.lock().await.remove(&name).is_some(); - Ok(Response::new(DeleteProviderResponse { deleted })) + Ok(Response::new(DeleteProviderResponse { + outcome: if deleted { + openshell_core::proto::DeletionOutcome::Completed.into() + } else { + openshell_core::proto::DeletionOutcome::AlreadyAbsent.into() + }, + })) } async fn delete_provider_profile( @@ -929,7 +944,13 @@ impl OpenShell for TestOpenShell { } let deleted = self.state.profiles.lock().await.remove(&id).is_some(); Ok(Response::new( - openshell_core::proto::DeleteProviderProfileResponse { deleted }, + openshell_core::proto::DeleteProviderProfileResponse { + outcome: if deleted { + openshell_core::proto::DeletionOutcome::Completed.into() + } else { + openshell_core::proto::DeletionOutcome::AlreadyAbsent.into() + }, + }, )) } diff --git a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs index b2a447029c..5bf1e0e1c5 100644 --- a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs +++ b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs @@ -300,7 +300,9 @@ impl OpenShell for TestOpenShell { .await .push(request.into_inner()); Ok(Response::new( - openshell_core::proto::DeleteSandboxTemplateResponse { deleted: true }, + openshell_core::proto::DeleteSandboxTemplateResponse { + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + }, )) } @@ -339,7 +341,10 @@ impl OpenShell for TestOpenShell { if let Some(message) = delete_failure { return Err(Status::internal(message)); } - Ok(Response::new(DeleteSandboxResponse { deleted: true })) + Ok(Response::new(DeleteSandboxResponse { + sandbox_id: String::new(), + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + })) } async fn get_sandbox_config( @@ -566,7 +571,9 @@ impl OpenShell for TestOpenShell { &self, _request: tonic::Request, ) -> Result, Status> { - Ok(Response::new(DeleteProviderResponse { deleted: true })) + Ok(Response::new(DeleteProviderResponse { + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + })) } type WatchSandboxStream = diff --git a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs index 6fb1cabcf6..50214b083f 100644 --- a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs +++ b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs @@ -179,7 +179,10 @@ impl OpenShell for TestOpenShell { &self, _request: tonic::Request, ) -> Result, Status> { - Ok(Response::new(DeleteSandboxResponse { deleted: true })) + Ok(Response::new(DeleteSandboxResponse { + sandbox_id: String::new(), + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + })) } async fn get_sandbox_config( @@ -408,7 +411,9 @@ impl OpenShell for TestOpenShell { &self, _request: tonic::Request, ) -> Result, Status> { - Ok(Response::new(DeleteProviderResponse { deleted: true })) + Ok(Response::new(DeleteProviderResponse { + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + })) } type WatchSandboxStream = diff --git a/crates/openshell-sdk/README.md b/crates/openshell-sdk/README.md index 3adbcba375..5f889f61f0 100644 --- a/crates/openshell-sdk/README.md +++ b/crates/openshell-sdk/README.md @@ -59,6 +59,22 @@ Curated calls without a workspace argument explicitly select the `default` workspace. Cross-workspace listing uses the separate `*_all_workspaces` methods and requires Platform Admin access. +For an accepted sandbox deletion, pass its original ID to `wait_deleted` so a +same-name replacement does not extend the wait. Both the default and +workspace-scoped clients accept the optional third argument; pass `None` to wait +for name absence instead. + +```rust +let deletion = client.delete_sandbox(name, openshell_sdk::DeleteOptions::default()).await?; +if deletion.outcome == openshell_sdk::DeletionOutcome::Accepted { + client.wait_deleted( + name, + std::time::Duration::from_secs(60), + deletion.sandbox_id.as_deref(), + ).await?; +} +``` + Curated `list_*` methods return a lazy `Pager`. Each `next_page()` call issues at most one RPC and returns a `Page` with its opaque continuation token. The explicit `list_all_*` conveniences exhaust that pager; `page_size` diff --git a/crates/openshell-sdk/src/client.rs b/crates/openshell-sdk/src/client.rs index af3863cd51..47f30dfe67 100644 --- a/crates/openshell-sdk/src/client.rs +++ b/crates/openshell-sdk/src/client.rs @@ -16,8 +16,9 @@ use crate::raw::AuthedGrpcClient; use crate::refresh::{RefreshedToken, TokenSource}; use crate::transport; use crate::types::{ - ExecOptions, ExecResult, Health, ListOptions, SandboxPhase, SandboxRef, SandboxSpec, - SandboxTemplateCreateSpec, SandboxTemplateListOptions, SandboxWorkloadTemplate, WorkspaceRef, + DeleteOptions, DeletionResult, ExecOptions, ExecResult, Health, ListOptions, SandboxPhase, + SandboxRef, SandboxSpec, SandboxTemplateCreateSpec, SandboxTemplateListOptions, + SandboxWorkloadTemplate, WorkspaceRef, }; use futures::StreamExt; use openshell_core::proto; @@ -273,17 +274,25 @@ impl OpenShellClient { } /// Delete a reusable sandbox template by name from the default workspace. - pub async fn delete_sandbox_template(&self, name: &str) -> Result { + pub async fn delete_sandbox_template( + &self, + name: &str, + opts: DeleteOptions, + ) -> Result { let response = self .unary(|mut grpc| { let request = proto::DeleteSandboxTemplateRequest { + allow_missing: opts.allow_missing, name: name.to_string(), workspace_scope: Some(proto::workspace_selector("default")), }; async move { grpc.delete_sandbox_template(request).await } }) .await?; - Ok(response.deleted) + Ok(DeletionResult { + outcome: response.outcome.into(), + sandbox_id: None, + }) } /// Fetch a sandbox by name. @@ -339,21 +348,23 @@ impl OpenShellClient { /// Delete a sandbox by name. /// - /// Returns `true` when the gateway acknowledges the deletion, `false` - /// when it was already absent. The sandbox may still be in - /// [`SandboxPhase::Deleting`] when this returns — pair with - /// [`OpenShellClient::wait_deleted`] when you need a terminal guarantee. - pub async fn delete_sandbox(&self, name: &str) -> Result { + /// An accepted outcome is not completion. The result identifies the original + /// sandbox; a same-name replacement is not part of this operation. + pub async fn delete_sandbox(&self, name: &str, opts: DeleteOptions) -> Result { let response = self .unary(|mut grpc| { let request = proto::DeleteSandboxRequest { + allow_missing: opts.allow_missing, name: name.to_string(), workspace_scope: Some(proto::workspace_selector("default")), }; async move { grpc.delete_sandbox(request).await } }) .await?; - Ok(response.deleted) + Ok(DeletionResult { + outcome: response.outcome.into(), + sandbox_id: (!response.sandbox_id.is_empty()).then_some(response.sandbox_id), + }) } /// Stop a sandbox by name. @@ -406,16 +417,26 @@ impl OpenShellClient { .await } - /// Poll until the sandbox is gone (gRPC `NotFound`) or the `timeout` - /// elapses. - pub async fn wait_deleted(&self, name: &str, timeout: Duration) -> Result<()> { + /// Poll until the sandbox is gone (gRPC `NotFound`) or the timeout elapses. + /// + /// Pass the deletion result's `sandbox_id` as `expected_sandbox_id` to also + /// complete when the name resolves to a different sandbox. With `None`, + /// waits for the name to be absent, including any same-name replacement. + pub async fn wait_deleted( + &self, + name: &str, + timeout: Duration, + expected_sandbox_id: Option<&str>, + ) -> Result<()> { let deadline = Instant::now() + timeout; let mut delay = Duration::from_millis(250); loop { match self.get_sandbox(name).await { Err(SdkError::NotFound { .. }) => return Ok(()), Err(other) => return Err(other), - Ok(snapshot) if snapshot.phase == SandboxPhase::Deleting => {} + Ok(snapshot) if expected_sandbox_id.is_some_and(|id| snapshot.id != id) => { + return Ok(()); + } Ok(_) => {} } if Instant::now() >= deadline { @@ -553,16 +574,24 @@ impl OpenShellClient { } /// Delete a workspace by name. - pub async fn delete_workspace(&self, name: &str) -> Result { + pub async fn delete_workspace( + &self, + name: &str, + opts: DeleteOptions, + ) -> Result { let response = self .unary(|mut grpc| { let request = proto::DeleteWorkspaceRequest { + allow_missing: opts.allow_missing, name: name.to_string(), }; async move { grpc.delete_workspace(request).await } }) .await?; - Ok(response.deleted) + Ok(DeletionResult { + outcome: response.outcome.into(), + sandbox_id: None, + }) } /// Run a command inside a sandbox and buffer stdout/stderr to the end. @@ -833,18 +862,26 @@ impl WorkspaceScopedClient { } /// Delete a reusable sandbox template by name in this workspace. - pub async fn delete_sandbox_template(&self, name: &str) -> Result { + pub async fn delete_sandbox_template( + &self, + name: &str, + opts: DeleteOptions, + ) -> Result { let response = self .client .unary(|mut grpc| { let request = proto::DeleteSandboxTemplateRequest { + allow_missing: opts.allow_missing, name: name.to_string(), workspace_scope: Some(proto::workspace_selector(&self.workspace)), }; async move { grpc.delete_sandbox_template(request).await } }) .await?; - Ok(response.deleted) + Ok(DeletionResult { + outcome: response.outcome.into(), + sandbox_id: None, + }) } /// Fetch a sandbox by name in this workspace. @@ -902,18 +939,22 @@ impl WorkspaceScopedClient { } /// Delete a sandbox by name in this workspace. - pub async fn delete_sandbox(&self, name: &str) -> Result { + pub async fn delete_sandbox(&self, name: &str, opts: DeleteOptions) -> Result { let response = self .client .unary(|mut grpc| { let request = proto::DeleteSandboxRequest { + allow_missing: opts.allow_missing, name: name.to_string(), workspace_scope: Some(proto::workspace_selector(&self.workspace)), }; async move { grpc.delete_sandbox(request).await } }) .await?; - Ok(response.deleted) + Ok(DeletionResult { + outcome: response.outcome.into(), + sandbox_id: (!response.sandbox_id.is_empty()).then_some(response.sandbox_id), + }) } /// Stop a sandbox by name in this workspace. @@ -980,13 +1021,25 @@ impl WorkspaceScopedClient { } /// Poll until the sandbox is gone (`NotFound`) or the timeout elapses. - pub async fn wait_deleted(&self, name: &str, timeout: Duration) -> Result<()> { + /// + /// Pass the deletion result's `sandbox_id` as `expected_sandbox_id` to also + /// complete when the name resolves to a different sandbox. With `None`, + /// waits for the name to be absent, including any same-name replacement. + pub async fn wait_deleted( + &self, + name: &str, + timeout: Duration, + expected_sandbox_id: Option<&str>, + ) -> Result<()> { let deadline = Instant::now() + timeout; let mut delay = Duration::from_millis(250); loop { match self.get_sandbox(name).await { Err(SdkError::NotFound { .. }) => return Ok(()), Err(other) => return Err(other), + Ok(snapshot) if expected_sandbox_id.is_some_and(|id| snapshot.id != id) => { + return Ok(()); + } Ok(_) => {} } if Instant::now() >= deadline { diff --git a/crates/openshell-sdk/src/lib.rs b/crates/openshell-sdk/src/lib.rs index b94b211f2f..e12e1cad8a 100644 --- a/crates/openshell-sdk/src/lib.rs +++ b/crates/openshell-sdk/src/lib.rs @@ -49,8 +49,9 @@ pub use error::SdkError; pub use pagination::{Page, Pager}; pub use refresh::{Refresh, RefreshError, RefreshedToken, TokenSource}; pub use types::{ - ExecOptions, ExecResult, Health, ListOptions, SandboxPhase, SandboxRef, SandboxResources, - SandboxServiceLevel, SandboxSpec, SandboxStartup, SandboxTemplateCreateSpec, - SandboxTemplateListOptions, SandboxWorkloadConfig, SandboxWorkloadTemplate, - SandboxWorkloadTemplateProvenance, SandboxWorkloadTemplateSpec, ServiceStatus, WorkspaceRef, + DeleteOptions, DeletionOutcome, DeletionResult, ExecOptions, ExecResult, Health, ListOptions, + SandboxPhase, SandboxRef, SandboxResources, SandboxServiceLevel, SandboxSpec, SandboxStartup, + SandboxTemplateCreateSpec, SandboxTemplateListOptions, SandboxWorkloadConfig, + SandboxWorkloadTemplate, SandboxWorkloadTemplateProvenance, SandboxWorkloadTemplateSpec, + ServiceStatus, WorkspaceRef, }; diff --git a/crates/openshell-sdk/src/types.rs b/crates/openshell-sdk/src/types.rs index a165c37124..3937462d00 100644 --- a/crates/openshell-sdk/src/types.rs +++ b/crates/openshell-sdk/src/types.rs @@ -17,6 +17,52 @@ use openshell_core::proto; use std::collections::HashMap; use std::time::Duration; +/// Missing targets are errors unless explicitly allowed. +#[derive(Clone, Copy, Debug, Default)] +pub struct DeleteOptions { + pub allow_missing: bool, +} + +/// A deletion acknowledgement is not necessarily completion. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum DeletionOutcome { + Unspecified, + Completed, + Accepted, + AlreadyAbsent, + Unknown(i32), +} + +impl From for DeletionOutcome { + fn from(value: i32) -> Self { + match proto::DeletionOutcome::try_from(value) { + Ok(proto::DeletionOutcome::Unspecified) => Self::Unspecified, + Ok(proto::DeletionOutcome::Completed) => Self::Completed, + Ok(proto::DeletionOutcome::Accepted) => Self::Accepted, + Ok(proto::DeletionOutcome::AlreadyAbsent) => Self::AlreadyAbsent, + Err(_) => Self::Unknown(value), + } + } +} + +#[test] +fn deletion_outcomes_preserve_unknown_values() { + assert_eq!(DeletionOutcome::from(0), DeletionOutcome::Unspecified); + assert_eq!(DeletionOutcome::from(1), DeletionOutcome::Completed); + assert_eq!(DeletionOutcome::from(2), DeletionOutcome::Accepted); + assert_eq!(DeletionOutcome::from(3), DeletionOutcome::AlreadyAbsent); + assert_eq!(DeletionOutcome::from(99), DeletionOutcome::Unknown(99)); +} + +/// Result for the original target, never a same-name replacement. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DeletionResult { + pub outcome: DeletionOutcome, + /// Present for sandbox deletions that found a target. + pub sandbox_id: Option, +} + /// Gateway health snapshot. #[derive(Clone, Debug)] #[non_exhaustive] diff --git a/crates/openshell-sdk/tests/client_mock.rs b/crates/openshell-sdk/tests/client_mock.rs index 5e04c74257..2cbe200335 100644 --- a/crates/openshell-sdk/tests/client_mock.rs +++ b/crates/openshell-sdk/tests/client_mock.rs @@ -61,6 +61,9 @@ struct MockState { last_workspace_request: Mutex>, get_calls: AtomicU32, phase_sequence: Vec, + get_sandbox_id: Option, + get_error: Option, + delete_response: Option, get_returns_not_found: bool, not_found_after: Option, paginate_list: bool, @@ -298,7 +301,7 @@ impl OpenShell for TestOpenShell { ) -> Result, Status> { *self.state.last_template_delete.lock().await = Some(request.into_inner()); Ok(Response::new(proto::DeleteSandboxTemplateResponse { - deleted: true, + outcome: proto::DeletionOutcome::Completed.into(), })) } @@ -345,6 +348,9 @@ impl OpenShell for TestOpenShell { selected_workspace(&req.workspace_scope).map(str::to_string); let count = self.state.get_calls.fetch_add(1, Ordering::SeqCst); + if let Some(error) = &self.state.get_error { + return Err(error.clone()); + } if self.state.get_returns_not_found { return Err(Status::not_found(format!("sandbox '{name}' not found"))); } @@ -362,8 +368,12 @@ impl OpenShell for TestOpenShell { .or_else(|| self.state.phase_sequence.last().copied()) .unwrap_or(proto::SandboxPhase::Ready); + let mut sandbox = sandbox_with_phase(&name, phase); + if let Some(id) = &self.state.get_sandbox_id { + sandbox.metadata.as_mut().unwrap().id.clone_from(id); + } Ok(Response::new(proto::SandboxResponse { - sandbox: Some(sandbox_with_phase(&name, phase)), + sandbox: Some(sandbox), })) } @@ -437,8 +447,12 @@ impl OpenShell for TestOpenShell { *self.state.last_delete_name.lock().await = Some(req.name); *self.state.last_delete_workspace.lock().await = selected_workspace(&req.workspace_scope).map(str::to_string); + if let Some(response) = &self.state.delete_response { + return Ok(Response::new(response.clone())); + } Ok(Response::new(proto::DeleteSandboxResponse { - deleted: true, + sandbox_id: String::new(), + outcome: proto::DeletionOutcome::Completed.into(), })) } @@ -871,7 +885,7 @@ impl OpenShell for TestOpenShell { ) -> Result, Status> { *self.state.last_workspace_request.lock().await = Some(request.into_inner().name); Ok(Response::new(proto::DeleteWorkspaceResponse { - deleted: true, + outcome: proto::DeletionOutcome::Completed.into(), })) } @@ -1052,8 +1066,11 @@ async fn sandbox_template_crud_uses_default_workspace() { assert!(observed_list.page_token.is_empty()); assert!(selects_all_workspaces(&observed_list.workspace_scope)); - let deleted = client.delete_sandbox_template("python").await.unwrap(); - assert!(deleted); + let deleted = client + .delete_sandbox_template("python", openshell_sdk::DeleteOptions::default()) + .await + .unwrap(); + assert_eq!(deleted.outcome, openshell_sdk::DeletionOutcome::Completed); let observed_delete = state.last_template_delete.lock().await.clone().unwrap(); assert_eq!(observed_delete.name, "python"); assert_eq!( @@ -1179,8 +1196,11 @@ async fn delete_sandbox_returns_server_ack() { let endpoint = start_mock(state.clone()).await; let client = connect(&endpoint).await; - let deleted = client.delete_sandbox("doomed").await.unwrap(); - assert!(deleted); + let deleted = client + .delete_sandbox("doomed", openshell_sdk::DeleteOptions::default()) + .await + .unwrap(); + assert_eq!(deleted.outcome, openshell_sdk::DeletionOutcome::Completed); let observed = state.last_delete_name.lock().await.clone(); assert_eq!(observed.as_deref(), Some("doomed")); @@ -1283,19 +1303,154 @@ async fn wait_ready_surfaces_error_phase() { #[tokio::test] async fn wait_deleted_returns_when_get_reports_not_found() { - let state = Arc::new(MockState { - phase_sequence: vec![proto::SandboxPhase::Deleting], - not_found_after: Some(2), - ..Default::default() - }); - let endpoint = start_mock(state.clone()).await; - let client = connect(&endpoint).await; + for scoped in [false, true] { + for expected_id in [None, Some("id-my-box")] { + let state = Arc::new(MockState { + phase_sequence: vec![proto::SandboxPhase::Deleting], + not_found_after: Some(1), + ..Default::default() + }); + let endpoint = start_mock(state.clone()).await; + let client = connect(&endpoint).await; + let timeout = Duration::from_secs(5); + if scoped { + client + .workspace("team") + .wait_deleted("my-box", timeout, expected_id) + .await + } else { + client.wait_deleted("my-box", timeout, expected_id).await + } + .unwrap(); + assert_eq!(state.get_calls.load(Ordering::SeqCst), 2); + } + } +} - client - .wait_deleted("my-box", Duration::from_secs(5)) - .await +#[tokio::test] +async fn wait_deleted_completes_on_replacement_after_accepted_deletion() { + for scoped in [false, true] { + let state = Arc::new(MockState { + get_sandbox_id: Some("replacement-id".to_string()), + delete_response: Some(proto::DeleteSandboxResponse { + outcome: proto::DeletionOutcome::Accepted.into(), + sandbox_id: "old-id".to_string(), + }), + ..Default::default() + }); + let endpoint = start_mock(state.clone()).await; + let client = connect(&endpoint).await; + let timeout = Duration::from_millis(50); + let result = if scoped { + client + .workspace("team") + .delete_sandbox("my-box", openshell_sdk::DeleteOptions::default()) + .await + } else { + client + .delete_sandbox("my-box", openshell_sdk::DeleteOptions::default()) + .await + } .unwrap(); - assert!(state.get_calls.load(Ordering::SeqCst) >= 3); + assert_eq!(result.outcome, openshell_sdk::DeletionOutcome::Accepted); + assert_eq!(result.sandbox_id.as_deref(), Some("old-id")); + if scoped { + client + .workspace("team") + .wait_deleted("my-box", timeout, result.sandbox_id.as_deref()) + .await + } else { + client + .wait_deleted("my-box", timeout, result.sandbox_id.as_deref()) + .await + } + .unwrap(); + assert_eq!(state.get_calls.load(Ordering::SeqCst), 1); + let workspace = if scoped { "team" } else { "default" }; + assert_eq!( + state.last_get_workspace.lock().await.as_deref(), + Some(workspace) + ); + assert_eq!( + state.last_delete_workspace.lock().await.as_deref(), + Some(workspace) + ); + } +} + +#[tokio::test] +async fn wait_deleted_does_not_complete_while_expected_identity_remains() { + for scoped in [false, true] { + let state = Arc::new(MockState { + get_sandbox_id: Some("old-id".to_string()), + ..Default::default() + }); + let endpoint = start_mock(state.clone()).await; + let client = connect(&endpoint).await; + let timeout = Duration::ZERO; + let error = if scoped { + client + .workspace("team") + .wait_deleted("my-box", timeout, Some("old-id")) + .await + } else { + client.wait_deleted("my-box", timeout, Some("old-id")).await + } + .unwrap_err(); + assert_eq!(error.code(), "connect"); + assert!(error.to_string().contains("timed out waiting")); + assert_eq!(state.get_calls.load(Ordering::SeqCst), 1); + } +} + +#[tokio::test] +async fn wait_deleted_without_identity_waits_for_replacement_to_disappear() { + for scoped in [false, true] { + let state = Arc::new(MockState { + get_sandbox_id: Some("replacement-id".to_string()), + not_found_after: Some(1), + ..Default::default() + }); + let endpoint = start_mock(state.clone()).await; + let client = connect(&endpoint).await; + let timeout = Duration::from_secs(5); + if scoped { + client + .workspace("team") + .wait_deleted("my-box", timeout, None) + .await + } else { + client.wait_deleted("my-box", timeout, None).await + } + .unwrap(); + assert_eq!(state.get_calls.load(Ordering::SeqCst), 2); + } +} + +#[tokio::test] +async fn wait_deleted_propagates_errors_other_than_not_found() { + for scoped in [false, true] { + for code in [tonic::Code::PermissionDenied, tonic::Code::Unavailable] { + let state = Arc::new(MockState { + get_error: Some(Status::new(code, "lookup failed")), + ..Default::default() + }); + let endpoint = start_mock(state.clone()).await; + let client = connect(&endpoint).await; + let timeout = Duration::from_secs(5); + let error = if scoped { + client + .workspace("team") + .wait_deleted("my-box", timeout, Some("old-id")) + .await + } else { + client.wait_deleted("my-box", timeout, Some("old-id")).await + } + .unwrap_err(); + assert_eq!(error.grpc_status().unwrap().code(), code); + assert_eq!(state.get_calls.load(Ordering::SeqCst), 1); + } + } } #[tokio::test] @@ -1564,8 +1719,11 @@ async fn workspace_scoped_sandbox_template_crud_passes_workspace() { let observed_all = state.last_template_list.lock().await.clone().unwrap(); assert!(selects_all_workspaces(&observed_all.workspace_scope)); - let deleted = ws.delete_sandbox_template("python").await.unwrap(); - assert!(deleted); + let deleted = ws + .delete_sandbox_template("python", openshell_sdk::DeleteOptions::default()) + .await + .unwrap(); + assert_eq!(deleted.outcome, openshell_sdk::DeletionOutcome::Completed); let observed_delete = state.last_template_delete.lock().await.clone().unwrap(); assert_eq!(observed_delete.name, "python"); assert_eq!( @@ -1581,8 +1739,11 @@ async fn workspace_scoped_delete_passes_workspace() { let client = connect(&endpoint).await; let ws = client.workspace("staging"); - let deleted = ws.delete_sandbox("doomed").await.unwrap(); - assert!(deleted); + let deleted = ws + .delete_sandbox("doomed", openshell_sdk::DeleteOptions::default()) + .await + .unwrap(); + assert_eq!(deleted.outcome, openshell_sdk::DeletionOutcome::Completed); let observed_ws = state.last_delete_workspace.lock().await.clone(); assert_eq!(observed_ws.as_deref(), Some("staging")); @@ -1658,8 +1819,11 @@ async fn delete_workspace_returns_ack() { let endpoint = start_mock(state.clone()).await; let client = connect(&endpoint).await; - let deleted = client.delete_workspace("doomed").await.unwrap(); - assert!(deleted); + let deleted = client + .delete_workspace("doomed", openshell_sdk::DeleteOptions::default()) + .await + .unwrap(); + assert_eq!(deleted.outcome, openshell_sdk::DeletionOutcome::Completed); let observed = state.last_workspace_request.lock().await.clone(); assert_eq!(observed.as_deref(), Some("doomed")); diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index 43b2ea78c0..4cca02d6ac 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -274,7 +274,18 @@ struct SandboxDeleteTarget { #[derive(Debug, Eq, PartialEq)] pub struct DeleteSandboxResult { pub sandbox_id: String, - pub deleted: bool, + pub outcome: openshell_core::proto::DeletionOutcome, +} + +#[cfg(test)] +impl DeleteSandboxResult { + fn acknowledged(&self) -> bool { + matches!( + self.outcome, + openshell_core::proto::DeletionOutcome::Completed + | openshell_core::proto::DeletionOutcome::Accepted + ) + } } #[derive(Debug)] @@ -1566,6 +1577,16 @@ impl ComputeRuntime { &self, workspace: &str, name: &str, + ) -> Result { + self.delete_sandbox_allow_missing(workspace, name, false) + .await + } + + pub(crate) async fn delete_sandbox_allow_missing( + &self, + workspace: &str, + name: &str, + allow_missing: bool, ) -> Result { // Resolve and acquire both request-side locks before spawning the // owned worker. Cancellation while any of these awaits is pending is @@ -1574,8 +1595,16 @@ impl ComputeRuntime { .store .get_message_by_name::(workspace, name) .await - .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? - .ok_or_else(|| Status::not_found("sandbox not found"))?; + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))?; + let Some(candidate) = candidate else { + if allow_missing { + return Ok(DeleteSandboxResult { + sandbox_id: String::new(), + outcome: openshell_core::proto::DeletionOutcome::AlreadyAbsent, + }); + } + return Err(Status::not_found("sandbox not found")); + }; let target = SandboxDeleteTarget { sandbox_id: candidate.object_id().to_string(), sandbox_name: candidate.object_name().to_string(), @@ -1624,7 +1653,7 @@ impl ComputeRuntime { self.cleanup_removed_sandbox_state(&target.sandbox_id); return Ok(DeleteSandboxResult { sandbox_id: target.sandbox_id, - deleted: true, + outcome: openshell_core::proto::DeletionOutcome::Completed, }); }; if current.object_name() != target.sandbox_name { @@ -1643,7 +1672,7 @@ impl ComputeRuntime { BeginDelete::AlreadyDeleting => { return Ok(DeleteSandboxResult { sandbox_id: target.sandbox_id, - deleted: true, + outcome: openshell_core::proto::DeletionOutcome::Accepted, }); } BeginDelete::Started(transition) => *transition, @@ -1676,20 +1705,30 @@ impl ComputeRuntime { match result { Ok(response) => { let deleted = response.into_inner().deleted; - if deleted { + let completed = if deleted { self.cleanup_local_state_if_sandbox_absent(&delete_guard, &target.sandbox_id) - .await?; - } else if !self - .remove_deleting_sandbox_record(&delete_guard, &target.sandbox_id) - .await - { - return Err(Status::internal( - "compute resource was absent, but gateway cleanup did not complete", - )); - } + .await? + } else { + if !self + .remove_deleting_sandbox_record(&delete_guard, &target.sandbox_id) + .await + { + return Err(Status::internal( + "compute resource was absent, but gateway cleanup did not complete", + )); + } + true + }; + // A driver's acknowledgement is not proof that asynchronous + // cleanup finished. Inspect the captured UUID, never the name: + // another sandbox may already have reused it. Ok(DeleteSandboxResult { sandbox_id: target.sandbox_id, - deleted, + outcome: if completed { + openshell_core::proto::DeletionOutcome::Completed + } else { + openshell_core::proto::DeletionOutcome::Accepted + }, }) } Err(err) => { @@ -3690,7 +3729,7 @@ impl ComputeRuntime { &self, delete_guard: &SandboxLifecycleGuard, sandbox_id: &str, - ) -> Result<(), Status> { + ) -> Result { let _guard = self.lock_global_for_lifecycle(delete_guard).await; let record = self .store @@ -3700,7 +3739,7 @@ impl ComputeRuntime { if record.is_none() { self.cleanup_removed_sandbox_state(sandbox_id); } - Ok(()) + Ok(record.is_none()) } fn cleanup_removed_sandbox_state(&self, sandbox_id: &str) { @@ -8687,7 +8726,7 @@ mod tests { .expect("delete did not finish") .unwrap() .unwrap() - .deleted + .acknowledged() ); stop_watch_loop(shutdown_tx, watch_handle).await; } @@ -8720,7 +8759,7 @@ mod tests { .unwrap() .unwrap() .unwrap() - .deleted + .acknowledged() ); assert!( tokio::time::timeout(Duration::from_secs(1), second) @@ -8728,7 +8767,7 @@ mod tests { .unwrap() .unwrap() .unwrap() - .deleted + .acknowledged() ); assert_eq!(driver.delete_calls(), 1); } @@ -8778,13 +8817,14 @@ mod tests { driver.set_delete_outcome(ControlledDeleteOutcome::Ok(false)); driver.release_delete(); - assert!( - !tokio::time::timeout(Duration::from_secs(1), second) + assert_eq!( + tokio::time::timeout(Duration::from_secs(1), second) .await .expect("second delete did not finish") .unwrap() .unwrap() - .deleted + .outcome, + openshell_core::proto::DeletionOutcome::Completed ); assert_eq!(driver.delete_calls(), 2); assert!( @@ -8890,7 +8930,7 @@ mod tests { runtime.store.put_message(&replacement).await.unwrap(); drop(delete_guard); - assert!(delete.await.unwrap().unwrap().deleted); + assert!(delete.await.unwrap().unwrap().acknowledged()); assert_eq!(driver.delete_calls(), 0); assert!( runtime @@ -8990,12 +9030,13 @@ mod tests { runtime.sandbox_index.update_from_sandbox(&sandbox); let session = seed_sandbox_owned_records(&runtime, &sandbox).await; - assert!( - !runtime + assert_eq!( + runtime .delete_sandbox("default", "sandbox-a") .await .unwrap() - .deleted + .outcome, + openshell_core::proto::DeletionOutcome::Completed ); assert!( runtime @@ -9054,7 +9095,10 @@ mod tests { .unwrap(); driver.release_delete(); - assert!(!delete.await.unwrap().unwrap().deleted); + assert_eq!( + delete.await.unwrap().unwrap().outcome, + openshell_core::proto::DeletionOutcome::Completed + ); assert!( runtime .store @@ -9111,19 +9155,24 @@ mod tests { #[tokio::test] async fn accepted_driver_delete_leaves_removal_to_watcher() { let driver = ControlledDriver::new(); - let runtime = test_runtime(driver).await; + let runtime = test_runtime(driver.clone()).await; let sandbox = sandbox_record("sb-1", "sandbox-a", SandboxPhase::Ready); runtime.store.put_message(&sandbox).await.unwrap(); let session = seed_sandbox_owned_records(&runtime, &sandbox).await; let mut watch_rx = runtime.sandbox_watch_bus.subscribe("sb-1"); - assert!( - runtime + for _ in 0..2 { + let result = runtime .delete_sandbox("default", "sandbox-a") .await - .unwrap() - .deleted - ); + .unwrap(); + assert_eq!( + result.outcome, + openshell_core::proto::DeletionOutcome::Accepted + ); + assert_eq!(result.sandbox_id, "sb-1"); + } + assert_eq!(driver.delete_calls(), 1); let stored = runtime .store @@ -9326,7 +9375,7 @@ mod tests { .expect("delete did not finish") .unwrap() .unwrap() - .deleted + .acknowledged() ); assert_sandbox_owned_records(&runtime, &sandbox, &session, false).await; assert!( @@ -9370,13 +9419,14 @@ mod tests { remove_sandbox_owned_records_from_store(&runtime, &sandbox).await; driver.release_delete(); - assert!( - !tokio::time::timeout(Duration::from_secs(1), delete) + assert_eq!( + tokio::time::timeout(Duration::from_secs(1), delete) .await .expect("delete did not finish") .unwrap() .unwrap() - .deleted + .outcome, + openshell_core::proto::DeletionOutcome::Completed ); assert_sandbox_owned_records(&runtime, &sandbox, &session, false).await; assert!( @@ -9669,7 +9719,7 @@ mod tests { .unwrap() .unwrap() .unwrap() - .deleted + .acknowledged() ); assert!( runtime @@ -11745,7 +11795,7 @@ mod tests { .delete_sandbox("default", "uds-sandbox") .await .unwrap() - .deleted + .acknowledged() ); let calls = driver.calls(); diff --git a/crates/openshell-server/src/grpc/mod.rs b/crates/openshell-server/src/grpc/mod.rs index b64a701c22..f66a0a011a 100644 --- a/crates/openshell-server/src/grpc/mod.rs +++ b/crates/openshell-server/src/grpc/mod.rs @@ -95,6 +95,18 @@ pub fn persistence_error_to_status( } } +/// Apply the public missing-target contract after authorization and parent checks. +fn deletion_outcome(deleted: bool, allow_missing: bool, resource: &str) -> Result { + use openshell_core::proto::DeletionOutcome; + if deleted { + Ok(DeletionOutcome::Completed.into()) + } else if allow_missing { + Ok(DeletionOutcome::AlreadyAbsent.into()) + } else { + Err(Status::not_found(format!("{resource} not found"))) + } +} + /// Extract the `Principal` from request extensions, or return `INTERNAL`. /// /// The middleware layer always inserts a `Principal` for authenticated methods, @@ -915,6 +927,9 @@ pub mod test_support { // Tests for mod-level utilities // --------------------------------------------------------------------------- +#[cfg(test)] +mod mutation_tests; + #[cfg(test)] mod tests { use super::*; diff --git a/crates/openshell-server/src/grpc/mutation_tests.rs b/crates/openshell-server/src/grpc/mutation_tests.rs new file mode 100644 index 0000000000..7cb14db25c --- /dev/null +++ b/crates/openshell-server/src/grpc/mutation_tests.rs @@ -0,0 +1,245 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +use openshell_core::proto::datamodel::v1::ObjectMeta; +use openshell_core::proto::{ + DeleteProviderProfileRequest, DeleteProviderRefreshRequest, DeleteProviderRequest, + DeleteSandboxRequest, DeleteSandboxTemplateRequest, DeleteServiceRequest, + DeleteWorkspaceRequest, DeletionOutcome, Provider, RemoveWorkspaceMemberRequest, + RevokeSshSessionRequest, Sandbox, SshSession, +}; +use tonic::Code; + +use super::test_support::{authed_request, test_server_state}; +use super::{provider, sandbox, service, workspace}; + +fn metadata(id: &str) -> ObjectMeta { + ObjectMeta { + id: id.into(), + name: id.into(), + workspace: "default".into(), + ..Default::default() + } +} + +#[tokio::test] +async fn every_delete_requires_explicit_allow_missing() { + let state = test_server_state().await; + state + .store + .put_message(&Sandbox { + metadata: Some(metadata("parent-sandbox")), + ..Default::default() + }) + .await + .unwrap(); + state + .store + .put_message(&Provider { + metadata: Some(metadata("parent-provider")), + ..Default::default() + }) + .await + .unwrap(); + + macro_rules! check { + ($handler:path, $request:expr) => {{ + let mut request = $request; + let err = $handler(&state, authed_request(request.clone())) + .await + .unwrap_err(); + assert_eq!( + err.code(), + Code::NotFound, + "{}: {err}", + stringify!($handler) + ); + request.allow_missing = true; + let response = $handler(&state, authed_request(request)) + .await + .unwrap() + .into_inner(); + assert_eq!( + response.outcome, + i32::from(DeletionOutcome::AlreadyAbsent), + "{}", + stringify!($handler) + ); + }}; + } + check!( + sandbox::handle_delete_sandbox, + DeleteSandboxRequest { + name: "missing".into(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + ..Default::default() + } + ); + check!( + sandbox::handle_delete_sandbox_template, + DeleteSandboxTemplateRequest { + name: "missing".into(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + ..Default::default() + } + ); + check!( + provider::handle_delete_provider, + DeleteProviderRequest { + name: "missing".into(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + ..Default::default() + } + ); + check!( + provider::handle_delete_provider_profile, + DeleteProviderProfileRequest { + id: "custom-missing".into(), + ..Default::default() + } + ); + check!( + provider::handle_delete_provider_refresh, + DeleteProviderRefreshRequest { + provider: "parent-provider".into(), + credential_key: "API_KEY".into(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + ..Default::default() + } + ); + check!( + service::handle_delete_service, + DeleteServiceRequest { + sandbox: "parent-sandbox".into(), + service: "missing".into(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + ..Default::default() + } + ); + check!( + workspace::handle_delete_workspace, + DeleteWorkspaceRequest { + name: "missing".into(), + ..Default::default() + } + ); + check!( + workspace::handle_remove_workspace_member, + RemoveWorkspaceMemberRequest { + principal_subject: "missing".into(), + ..Default::default() + } + ); + check!( + sandbox::handle_revoke_ssh_session, + RevokeSshSessionRequest { + token: "missing".into(), + ..Default::default() + } + ); +} + +#[tokio::test] +async fn allow_missing_does_not_hide_missing_parents_or_invalid_requests() { + let state = test_server_state().await; + let err = service::handle_delete_service( + &state, + authed_request(DeleteServiceRequest { + sandbox: "missing-parent".into(), + allow_missing: true, + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + ..Default::default() + }), + ) + .await + .unwrap_err(); + assert_eq!(err.code(), Code::NotFound); + let err = provider::handle_delete_provider_refresh( + &state, + authed_request(DeleteProviderRefreshRequest { + provider: "missing-parent".into(), + credential_key: "API_KEY".into(), + allow_missing: true, + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + }), + ) + .await + .unwrap_err(); + assert_eq!(err.code(), Code::NotFound); + let err = workspace::handle_remove_workspace_member( + &state, + authed_request(RemoveWorkspaceMemberRequest { + workspace: "missing-parent".into(), + principal_subject: "missing".into(), + allow_missing: true, + }), + ) + .await + .unwrap_err(); + assert_eq!(err.code(), Code::NotFound); + let err = sandbox::handle_revoke_ssh_session( + &state, + authed_request(RevokeSshSessionRequest { + allow_missing: true, + ..Default::default() + }), + ) + .await + .unwrap_err(); + assert_eq!(err.code(), Code::InvalidArgument); + let err = sandbox::handle_revoke_ssh_session( + &state, + tonic::Request::new(RevokeSshSessionRequest { + token: "missing".into(), + allow_missing: true, + }), + ) + .await + .unwrap_err(); + // Authentication middleware must inject a principal before dispatch. + assert_eq!(err.code(), Code::Internal); +} + +#[tokio::test] +async fn repeated_revocation_completes_without_another_write() { + let state = test_server_state().await; + state + .store + .put_message(&SshSession { + metadata: Some(metadata("session-token")), + token: "session-token".into(), + ..Default::default() + }) + .await + .unwrap(); + let request = RevokeSshSessionRequest { + token: "session-token".into(), + allow_missing: false, + }; + let first = sandbox::handle_revoke_ssh_session(&state, authed_request(request.clone())) + .await + .unwrap() + .into_inner(); + assert_eq!(first.outcome, i32::from(DeletionOutcome::Completed)); + let stored = state + .store + .get_message::("session-token") + .await + .unwrap() + .unwrap(); + assert!(stored.revoked); + let second = sandbox::handle_revoke_ssh_session(&state, authed_request(request)) + .await + .unwrap() + .into_inner(); + assert_eq!(second.outcome, i32::from(DeletionOutcome::Completed)); + assert_eq!( + state + .store + .get_message::("session-token") + .await + .unwrap() + .unwrap(), + stored + ); +} diff --git a/crates/openshell-server/src/grpc/provider.rs b/crates/openshell-server/src/grpc/provider.rs index 9ab7aca417..21e2b3c18c 100644 --- a/crates/openshell-server/src/grpc/provider.rs +++ b/crates/openshell-server/src/grpc/provider.rs @@ -608,9 +608,12 @@ pub(super) async fn delete_provider_record_with_credentials( .await?; store - .delete_by_name(Provider::object_type(), workspace, name) + .delete(Provider::object_type(), provider.object_id()) .await - .map_err(|e| Status::internal(format!("delete provider failed: {e}"))) + .map_err(|e| Status::internal(format!("delete provider failed: {e}")))?; + // This call observed the original target. A concurrent removal is also + // completion, and must not remove a replacement with the same name. + Ok(true) } /// Iterate over every `Sandbox` in the store and collect items produced by @@ -2962,9 +2965,11 @@ pub(super) async fn handle_delete_provider_profile( .get_message_by_name::(&workspace, &id) .await .map_err(|e| Status::internal(format!("fetch provider profile failed: {e}")))?; - if existing.is_none() { - return Err(Status::not_found("provider profile not found")); - } + let Some(existing) = existing else { + return Ok(Response::new(DeleteProviderProfileResponse { + outcome: super::deletion_outcome(false, req.allow_missing, "provider profile")?, + })); + }; let blocking_providers = providers_using_profile(state.store.as_ref(), &workspace, &id).await?; if !blocking_providers.is_empty() { @@ -2974,13 +2979,15 @@ pub(super) async fn handle_delete_provider_profile( ))); } - let deleted = state + state .store - .delete_by_name(StoredProviderProfile::object_type(), &workspace, &id) + .delete(StoredProviderProfile::object_type(), existing.object_id()) .await .map_err(|e| Status::internal(format!("delete provider profile failed: {e}")))?; - Ok(Response::new(DeleteProviderProfileResponse { deleted })) + Ok(Response::new(DeleteProviderProfileResponse { + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + })) } pub(super) fn get_provider_type_profile_for_scope( @@ -4835,12 +4842,19 @@ pub(super) async fn handle_delete_provider_refresh( credential_key, ) .await?; - let deleted_refresh_state = crate::provider_refresh::delete_refresh_state_with_credentials( + let Some(refresh_state) = existing_refresh_state else { + return Ok(Response::new(DeleteProviderRefreshResponse { + outcome: super::deletion_outcome( + false, + request.allow_missing, + "provider refresh configuration", + )?, + })); + }; + crate::provider_refresh::delete_observed_refresh_state_with_credentials( state.store.as_ref(), &state.credentials, - &workspace, - provider.object_id(), - credential_key, + refresh_state.clone(), ) .await?; @@ -4850,9 +4864,7 @@ pub(super) async fn handle_delete_provider_refresh( // inside the CAS closure so they see the current stored provider — deciding // from the snapshot read above would let a concurrent rotation or provider // update land between the read and the write and then be clobbered (CWE-362). - if let Some(refresh_state) = existing_refresh_state - && crate::provider_refresh::refresh_has_expiration(&refresh_state) - { + if crate::provider_refresh::refresh_has_expiration(&refresh_state) { let refresh_expires_at_ms = refresh_state.expires_at_ms; let owned_keys: Vec = std::iter::once(credential_key.to_string()) .chain(refresh_state.additional_output_keys.into_values()) @@ -4871,7 +4883,7 @@ pub(super) async fn handle_delete_provider_refresh( } Ok(Response::new(DeleteProviderRefreshResponse { - deleted: deleted_refresh_state, + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), })) } @@ -4903,13 +4915,15 @@ pub(super) async fn handle_delete_provider( .await; match result { Ok(deleted) => { - let outcome = TelemetryOutcome::from_success(deleted); + let outcome = TelemetryOutcome::from_success(deleted || req.allow_missing); emit_provider_profile_lifecycle( provider_profile.unwrap_or(TelemetryProviderProfile::Custom), LifecycleOperation::Delete, outcome, ); - Ok(Response::new(DeleteProviderResponse { deleted })) + Ok(Response::new(DeleteProviderResponse { + outcome: super::deletion_outcome(deleted, req.allow_missing, "provider")?, + })) } Err(err) => { emit_provider_profile_lifecycle( @@ -6482,6 +6496,7 @@ mod tests { let deleted = handle_delete_provider_profile( &state, authed_request(DeleteProviderProfileRequest { + allow_missing: false, id: " Alex-API ".to_string(), workspace: "default".to_string(), }), @@ -6489,7 +6504,10 @@ mod tests { .await .unwrap() .into_inner(); - assert!(deleted.deleted); + assert_eq!( + deleted.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); assert_eq!(state.credentials.stored_credential_count(), Some(0)); } @@ -6760,6 +6778,7 @@ mod tests { let builtin_err = handle_delete_provider_profile( &state, authed_request(DeleteProviderProfileRequest { + allow_missing: false, id: "github".to_string(), workspace: "default".to_string(), }), @@ -6800,6 +6819,7 @@ mod tests { let in_use_err = handle_delete_provider_profile( &state, authed_request(DeleteProviderProfileRequest { + allow_missing: false, id: "custom-api".to_string(), workspace: "default".to_string(), }), @@ -6851,6 +6871,7 @@ mod tests { let err = handle_delete_provider_profile( &state, authed_request(DeleteProviderProfileRequest { + allow_missing: false, id: "global-custom".to_string(), workspace: String::new(), }), @@ -7066,6 +7087,7 @@ mod tests { let deleted = handle_delete_provider_refresh( &state, authed_request(DeleteProviderRefreshRequest { + allow_missing: false, provider: "msgraph".to_string(), credential_key: "MS_GRAPH_ACCESS_TOKEN".to_string(), workspace_scope: Some(openshell_core::proto::workspace_selector( @@ -7076,7 +7098,10 @@ mod tests { .await .unwrap() .into_inner(); - assert!(deleted.deleted); + assert_eq!( + deleted.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); assert_eq!(state.credentials.stored_credential_count(), Some(0)); let status_after_delete = handle_get_provider_refresh_status( @@ -7465,6 +7490,7 @@ mod tests { handle_delete_provider_refresh( &state, authed_request(DeleteProviderRefreshRequest { + allow_missing: false, provider: "provider-a".to_string(), credential_key: "REFRESH_TOKEN".to_string(), workspace_scope: Some(openshell_core::proto::workspace_selector( @@ -7647,6 +7673,7 @@ mod tests { let deleted = handle_delete_provider_refresh( &state, authed_request(DeleteProviderRefreshRequest { + allow_missing: false, provider: "msgraph".to_string(), credential_key: "MS_GRAPH_ACCESS_TOKEN".to_string(), workspace_scope: Some(openshell_core::proto::workspace_selector( @@ -7657,7 +7684,10 @@ mod tests { .await .unwrap() .into_inner(); - assert!(deleted.deleted); + assert_eq!( + deleted.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); let provider_after_delete = state .store @@ -7764,6 +7794,7 @@ mod tests { handle_delete_provider_refresh( &state, authed_request(DeleteProviderRefreshRequest { + allow_missing: false, provider: "aws-delete".to_string(), credential_key: "AWS_ACCESS_KEY_ID".to_string(), workspace_scope: Some(openshell_core::proto::workspace_selector( @@ -8247,6 +8278,7 @@ mod tests { let deleted = handle_delete_provider_profile( &state, authed_request(DeleteProviderProfileRequest { + allow_missing: false, id: "custom-api".to_string(), workspace: "default".to_string(), }), @@ -8254,7 +8286,10 @@ mod tests { .await .unwrap() .into_inner(); - assert!(deleted.deleted); + assert_eq!( + deleted.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); let missing = handle_get_provider_profile( &state, @@ -8283,6 +8318,7 @@ mod tests { handle_delete_provider_profile( &task_state, authed_request(DeleteProviderProfileRequest { + allow_missing: false, id: "guarded-delete".to_string(), workspace: "default".to_string(), }), @@ -8303,7 +8339,10 @@ mod tests { .expect("join delete task") .expect("delete should succeed") .into_inner(); - assert!(response.deleted); + assert_eq!( + response.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); } #[tokio::test] @@ -13549,6 +13588,7 @@ mod tests { let deleted = handle_delete_provider( &state, authed_request(DeleteProviderRequest { + allow_missing: false, name: "shared-name".to_string(), workspace_scope: Some(openshell_core::proto::workspace_selector( "default".to_string(), @@ -13558,7 +13598,10 @@ mod tests { .await .unwrap() .into_inner(); - assert!(deleted.deleted); + assert_eq!( + deleted.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); let listed = handle_list_providers( &state, @@ -13715,6 +13758,7 @@ mod tests { let delete_error = handle_delete_provider_profile( &state, authed_request(DeleteProviderProfileRequest { + allow_missing: false, id: "nonexistent".to_string(), workspace: String::new(), }), @@ -14035,7 +14079,11 @@ mod tests { async move { handle_delete_provider_profile( &state, - authed_request(DeleteProviderProfileRequest { id, workspace }), + authed_request(DeleteProviderProfileRequest { + allow_missing: false, + id, + workspace, + }), ) .await .unwrap() @@ -14043,8 +14091,14 @@ mod tests { } }; - assert!(delete("e2e-platform", "").await.deleted); - assert!(delete("e2e-workspace", "default").await.deleted); + assert_eq!( + delete("e2e-platform", "").await.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); + assert_eq!( + delete("e2e-workspace", "default").await.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); } #[tokio::test] @@ -14544,6 +14598,7 @@ mod tests { let err = handle_delete_provider_refresh( &state, non_member_request(DeleteProviderRefreshRequest { + allow_missing: false, workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), ..Default::default() }), @@ -14559,6 +14614,7 @@ mod tests { let err = handle_delete_provider( &state, non_member_request(DeleteProviderRequest { + allow_missing: false, workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), ..Default::default() }), @@ -14651,6 +14707,7 @@ mod tests { let err = handle_delete_provider_profile( &state, non_member_request(DeleteProviderProfileRequest { + allow_missing: false, workspace: "no-such-ws".into(), ..Default::default() }), diff --git a/crates/openshell-server/src/grpc/sandbox.rs b/crates/openshell-server/src/grpc/sandbox.rs index e806529fbe..429a756c7c 100644 --- a/crates/openshell-server/src/grpc/sandbox.rs +++ b/crates/openshell-server/src/grpc/sandbox.rs @@ -978,7 +978,9 @@ pub(super) async fn handle_delete_sandbox_template( ) .await .map_err(|e| Status::internal(format!("delete sandbox template failed: {e}")))?; - Ok(Response::new(DeleteSandboxTemplateResponse { deleted })) + Ok(Response::new(DeleteSandboxTemplateResponse { + outcome: super::deletion_outcome(deleted, req.allow_missing, "sandbox template")?, + })) } fn validate_sandbox_workload_template(template: &SandboxWorkloadTemplate) -> Result<(), Status> { @@ -1314,7 +1316,7 @@ pub(super) async fn handle_delete_sandbox( ) -> Result, Status> { let result = handle_delete_sandbox_inner(state, request).await; let outcome = match &result { - Ok(response) if response.get_ref().deleted => TelemetryOutcome::Success, + Ok(_) => TelemetryOutcome::Success, _ => TelemetryOutcome::Failure, }; openshell_core::telemetry::emit_lifecycle( @@ -1347,13 +1349,17 @@ async fn handle_delete_sandbox_inner( .await? .name; - let result = state.compute.delete_sandbox(&workspace, &name).await?; - if result.deleted { + let result = state + .compute + .delete_sandbox_allow_missing(&workspace, &name, req.allow_missing) + .await?; + if !result.sandbox_id.is_empty() { state.telemetry.end_sandbox_session(&result.sandbox_id); } info!(sandbox_name = %name, "DeleteSandbox request completed successfully"); Ok(Response::new(DeleteSandboxResponse { - deleted: result.deleted, + outcome: result.outcome.into(), + sandbox_id: result.sandbox_id, })) } @@ -2496,7 +2502,8 @@ pub(super) async fn handle_revoke_ssh_session( request: Request, ) -> Result, Status> { let principal = super::extract_principal(&request)?; - let token = request.into_inner().token; + let req = request.into_inner(); + let token = req.token; if token.is_empty() { return Err(Status::invalid_argument("token is required")); } @@ -2508,7 +2515,9 @@ pub(super) async fn handle_revoke_ssh_session( .map_err(|e| Status::internal(format!("fetch ssh session failed: {e}")))?; let Some(mut session) = session else { - return Ok(Response::new(RevokeSshSessionResponse { revoked: false })); + return Ok(Response::new(RevokeSshSessionResponse { + outcome: super::deletion_outcome(false, req.allow_missing, "ssh session")?, + })); }; authorize_sandbox_workspace( &state.store, @@ -2526,6 +2535,12 @@ pub(super) async fn handle_revoke_ssh_session( } })?; + if session.revoked { + return Ok(Response::new(RevokeSshSessionResponse { + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + })); + } + let resource_version = session .metadata .as_ref() @@ -2557,7 +2572,9 @@ pub(super) async fn handle_revoke_ssh_session( .await .map_err(|e| super::persistence_error_to_status(e, "revoke ssh session"))?; - Ok(Response::new(RevokeSshSessionResponse { revoked: true })) + Ok(Response::new(RevokeSshSessionResponse { + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + })) } // --------------------------------------------------------------------------- @@ -3741,6 +3758,7 @@ mod tests { handle_delete_sandbox_inner( &delete_state, authed_request(DeleteSandboxRequest { + allow_missing: false, name: "reused-name".to_string(), workspace_scope: Some(openshell_core::proto::workspace_selector( "default".to_string(), @@ -3768,7 +3786,10 @@ mod tests { drop(global_guard); let response = delete.await.unwrap().unwrap().into_inner(); - assert!(response.deleted); + assert_eq!( + response.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); assert!( state .store @@ -4956,6 +4977,7 @@ mod tests { let deleted = handle_delete_sandbox_template( &state, authed_request(DeleteSandboxTemplateRequest { + allow_missing: false, name: "gpu-kata".to_string(), workspace_scope: Some(openshell_core::proto::workspace_selector( "default".to_string(), @@ -4965,7 +4987,10 @@ mod tests { .await .expect("template delete should succeed") .into_inner(); - assert!(deleted.deleted); + assert_eq!( + deleted.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); let missing = handle_get_sandbox_template( &state, @@ -6085,7 +6110,10 @@ mod tests { let handle1 = tokio::spawn(async move { handle_revoke_ssh_session( &state1, - authed_request(RevokeSshSessionRequest { token: token1 }), + authed_request(RevokeSshSessionRequest { + allow_missing: false, + token: token1, + }), ) .await }); @@ -6095,7 +6123,10 @@ mod tests { let handle2 = tokio::spawn(async move { handle_revoke_ssh_session( &state2, - authed_request(RevokeSshSessionRequest { token: token2 }), + authed_request(RevokeSshSessionRequest { + allow_missing: false, + token: token2, + }), ) .await }); @@ -6106,7 +6137,11 @@ mod tests { // One should succeed, one may fail with ABORTED due to CAS conflict let successes = [&result1, &result2] .iter() - .filter(|r| r.is_ok() && r.as_ref().unwrap().get_ref().revoked) + .filter(|r| { + r.is_ok() + && r.as_ref().unwrap().get_ref().outcome() + == openshell_core::proto::DeletionOutcome::Completed + }) .count(); // At least one should succeed in revoking @@ -6778,6 +6813,7 @@ mod tests { let err = handle_delete_sandbox( &state, non_member_request(DeleteSandboxRequest { + allow_missing: false, workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), name: "any".into(), }), @@ -6909,6 +6945,7 @@ mod tests { handle_revoke_ssh_session( &state, authed_request(RevokeSshSessionRequest { + allow_missing: false, token: token.clone(), }), ) diff --git a/crates/openshell-server/src/grpc/service.rs b/crates/openshell-server/src/grpc/service.rs index f46c5b426f..2d142cd082 100644 --- a/crates/openshell-server/src/grpc/service.rs +++ b/crates/openshell-server/src/grpc/service.rs @@ -267,15 +267,22 @@ pub(super) async fn handle_delete_service( validate_endpoint_name("sandbox", &req.sandbox, MAX_SANDBOX_NAME_LEN)?; validate_optional_endpoint_name("service", &req.service, MAX_SERVICE_NAME_LEN)?; + state + .store + .get_message_by_name::(&workspace, &req.sandbox) + .await + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? + .ok_or_else(|| Status::not_found("sandbox not found"))?; let endpoint = get_service_endpoint(state, &workspace, &req.sandbox, &req.service).await?; let Some(endpoint) = endpoint else { - return Ok(Response::new(DeleteServiceResponse { deleted: false })); + return Ok(Response::new(DeleteServiceResponse { + outcome: super::deletion_outcome(false, req.allow_missing, "service endpoint")?, + })); }; - let key = service_routing::endpoint_key(&req.sandbox, &req.service); let deleted = state .store - .delete_by_name(ServiceEndpoint::object_type(), &workspace, &key) + .delete(ServiceEndpoint::object_type(), endpoint.object_id()) .await .map_err(|e| Status::internal(format!("delete endpoint failed: {e}")))?; @@ -283,7 +290,9 @@ pub(super) async fn handle_delete_service( service_routing::emit_service_endpoint_delete_event(&endpoint); } - Ok(Response::new(DeleteServiceResponse { deleted })) + Ok(Response::new(DeleteServiceResponse { + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + })) } async fn get_service_endpoint( @@ -477,6 +486,7 @@ mod tests { let deleted = handle_delete_service( &state, authed_request(DeleteServiceRequest { + allow_missing: false, sandbox: "my-sandbox".to_string(), service: "web".to_string(), workspace_scope: Some(openshell_core::proto::workspace_selector( @@ -487,7 +497,10 @@ mod tests { .await .unwrap() .into_inner(); - assert!(deleted.deleted); + assert_eq!( + deleted.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); let err = handle_get_service( &state, @@ -826,6 +839,7 @@ mod tests { let deleted = handle_delete_service( &state, authed_request(DeleteServiceRequest { + allow_missing: false, sandbox: "my-sandbox".to_string(), service: "web".to_string(), workspace_scope: Some(openshell_core::proto::workspace_selector( @@ -836,7 +850,10 @@ mod tests { .await .unwrap() .into_inner(); - assert!(deleted.deleted); + assert_eq!( + deleted.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); let listed = handle_list_services( &state, @@ -977,6 +994,7 @@ mod tests { let err = handle_delete_service( &state, non_member_request(DeleteServiceRequest { + allow_missing: false, workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), ..Default::default() }), diff --git a/crates/openshell-server/src/grpc/workspace.rs b/crates/openshell-server/src/grpc/workspace.rs index 82440c1b59..729dfd79ee 100644 --- a/crates/openshell-server/src/grpc/workspace.rs +++ b/crates/openshell-server/src/grpc/workspace.rs @@ -292,7 +292,8 @@ pub(super) async fn handle_delete_workspace( state: &Arc, request: Request, ) -> Result, Status> { - let name = request.into_inner().name; + let req = request.into_inner(); + let name = req.name; if name.is_empty() { return Err(Status::invalid_argument("name is required")); } @@ -302,12 +303,16 @@ pub(super) async fn handle_delete_workspace( )); } - let ws: Workspace = state + let ws: Option = state .store .get_message_by_name("", &name) .await - .map_err(|e| Status::internal(format!("fetch workspace failed: {e}")))? - .ok_or_else(|| Status::not_found(format!("workspace '{name}' not found")))?; + .map_err(|e| Status::internal(format!("fetch workspace failed: {e}")))?; + let Some(ws) = ws else { + return Ok(Response::new(DeleteWorkspaceResponse { + outcome: super::deletion_outcome(false, req.allow_missing, "workspace")?, + })); + }; let ws_id = ws .metadata @@ -344,14 +349,15 @@ pub(super) async fn handle_delete_workspace( } Err(e) => { if matches!(e, crate::persistence::PersistenceError::Conflict { .. }) { - let refreshed: Option = state - .store - .get_message_by_name("", &name) - .await - .map_err(|e| Status::internal(format!("workspace re-fetch failed: {e}")))?; - let refreshed = refreshed.ok_or_else(|| { - Status::not_found(format!("workspace '{name}' not found")) - })?; + let refreshed: Option = + state.store.get_message(&ws_id).await.map_err(|e| { + Status::internal(format!("workspace re-fetch failed: {e}")) + })?; + let Some(refreshed) = refreshed else { + return Ok(Response::new(DeleteWorkspaceResponse { + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + })); + }; let now_terminating = refreshed .metadata .as_ref() @@ -431,7 +437,7 @@ pub(super) async fn handle_delete_workspace( ) })?; - let deleted = state + let _deleted = state .store .delete_if(Workspace::object_type(), &ws_id, delete_version) .await @@ -443,7 +449,9 @@ pub(super) async fn handle_delete_workspace( } })?; - Ok(Response::new(DeleteWorkspaceResponse { deleted })) + Ok(Response::new(DeleteWorkspaceResponse { + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + })) } pub(super) async fn handle_add_workspace_member( @@ -585,7 +593,9 @@ pub(super) async fn handle_remove_workspace_member( .await .map_err(|e| Status::internal(format!("remove workspace member failed: {e}")))?; - Ok(Response::new(RemoveWorkspaceMemberResponse { removed })) + Ok(Response::new(RemoveWorkspaceMemberResponse { + outcome: super::deletion_outcome(removed, req.allow_missing, "workspace member")?, + })) } pub(super) async fn handle_list_workspace_members( @@ -801,6 +811,7 @@ mod tests { let err = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "ephemeral".to_string(), }), ) @@ -822,13 +833,17 @@ mod tests { let resp = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "ephemeral".to_string(), }), ) .await .unwrap() .into_inner(); - assert!(resp.deleted); + assert_eq!( + resp.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); } #[tokio::test] @@ -863,6 +878,7 @@ mod tests { let err = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "templated".to_string(), }), ) @@ -888,13 +904,17 @@ mod tests { let resp = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "templated".to_string(), }), ) .await .unwrap() .into_inner(); - assert!(resp.deleted); + assert_eq!( + resp.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); } #[tokio::test] @@ -932,6 +952,7 @@ mod tests { let err = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "sessioned".to_string(), }), ) @@ -977,6 +998,7 @@ mod tests { let err = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "profiles-ws".to_string(), }), ) @@ -1002,13 +1024,17 @@ mod tests { let resp = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "profiles-ws".to_string(), }), ) .await .unwrap() .into_inner(); - assert!(resp.deleted); + assert_eq!( + resp.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); } #[tokio::test] @@ -1018,6 +1044,7 @@ mod tests { let err = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "default".to_string(), }), ) @@ -1090,6 +1117,7 @@ mod tests { let resp = handle_remove_workspace_member( &state, authed_request(RemoveWorkspaceMemberRequest { + allow_missing: false, workspace: "default".to_string(), principal_subject: "charlie@example.com".to_string(), }), @@ -1097,7 +1125,10 @@ mod tests { .await .unwrap() .into_inner(); - assert!(resp.removed); + assert_eq!( + resp.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); let list = handle_list_workspace_members( &state, @@ -1195,13 +1226,17 @@ mod tests { let resp = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "cleanup-test".to_string(), }), ) .await .unwrap() .into_inner(); - assert!(resp.deleted); + assert_eq!( + resp.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); // Membership records should have been cleaned up. let remaining: Vec = state @@ -1284,6 +1319,7 @@ mod tests { let err = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "term-test".to_string(), }), ) @@ -1337,6 +1373,7 @@ mod tests { let _ = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "dying-ws".to_string(), }), ) @@ -1382,6 +1419,7 @@ mod tests { let _ = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "idempotent-ws".to_string(), }), ) @@ -1398,13 +1436,17 @@ mod tests { let resp = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "idempotent-ws".to_string(), }), ) .await .unwrap() .into_inner(); - assert!(resp.deleted); + assert_eq!( + resp.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); } #[tokio::test] @@ -1424,6 +1466,7 @@ mod tests { let err = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "cleanup-retry".to_string(), }), ) @@ -1442,13 +1485,17 @@ mod tests { let retry = handle_delete_workspace( &state, Request::new(DeleteWorkspaceRequest { + allow_missing: false, name: "cleanup-retry".to_string(), }), ) .await .unwrap() .into_inner(); - assert!(retry.deleted); + assert_eq!( + retry.outcome(), + openshell_core::proto::DeletionOutcome::Completed + ); assert!( state .store @@ -1587,6 +1634,7 @@ mod tests { let err = handle_remove_workspace_member( &state, non_member_request(RemoveWorkspaceMemberRequest { + allow_missing: false, workspace: "no-such-ws".into(), ..Default::default() }), diff --git a/crates/openshell-server/src/provider_refresh.rs b/crates/openshell-server/src/provider_refresh.rs index 43a70ee89d..0251c02122 100644 --- a/crates/openshell-server/src/provider_refresh.rs +++ b/crates/openshell-server/src/provider_refresh.rs @@ -211,10 +211,20 @@ pub async fn delete_refresh_state_with_credentials( provider_id: &str, credential_key: &str, ) -> Result { - let Some(mut state) = get_refresh_state(store, workspace, provider_id, credential_key).await? + let Some(state) = get_refresh_state(store, workspace, provider_id, credential_key).await? else { return Ok(false); }; + delete_observed_refresh_state_with_credentials(store, credentials, state).await?; + Ok(true) +} + +/// Delete the observed refresh identity without resolving its name again. +pub async fn delete_observed_refresh_state_with_credentials( + store: &Store, + credentials: &crate::credentials::CredentialRuntime, + mut state: StoredProviderCredentialRefreshState, +) -> Result<(), Status> { let mut version = state .metadata .as_ref() @@ -231,11 +241,24 @@ pub async fn delete_refresh_state_with_credentials( state.authorization_epoch = uuid::Uuid::new_v4().to_string(); state.status = "deleting".to_string(); state.next_refresh_at_ms = i64::MAX; - version = persist_refresh_state_if_current(store, &state, version) - .await? - .ok_or_else(|| { - Status::aborted("provider refresh was concurrently modified during deletion") - })?; + let Some(current_version) = + persist_refresh_state_if_current(store, &state, version).await? + else { + if store + .get_message::(state.object_id()) + .await + .map_err(|err| { + Status::internal(format!("fetch provider refresh state failed: {err}")) + })? + .is_none() + { + return Ok(()); + } + return Err(Status::aborted( + "provider refresh was concurrently modified during deletion", + )); + }; + version = current_version; if let Some(metadata) = state.metadata.as_mut() { metadata.resource_version = version; } @@ -260,7 +283,8 @@ pub async fn delete_refresh_state_with_credentials( Status::aborted("provider refresh was concurrently modified during deletion") } other => Status::internal(format!("delete provider refresh state failed: {other}")), - }) + })?; + Ok(()) } pub async fn delete_refresh_states_for_provider_with_credentials( @@ -4247,6 +4271,55 @@ mod tests { assert!(err.message().contains("aws_session_token requires")); } + #[tokio::test] + async fn deletion_of_observed_refresh_preserves_same_name_replacement() { + use super::delete_observed_refresh_state_with_credentials; + use crate::persistence::ObjectType; + let store = test_store().await; + let original = StoredProviderCredentialRefreshState { + metadata: Some(ObjectMeta { + id: "original-id".into(), + name: "refresh-name".into(), + workspace: "default".into(), + ..Default::default() + }), + ..Default::default() + }; + store.put_message(&original).await.unwrap(); + let observed = store + .get_message::("original-id") + .await + .unwrap() + .unwrap(); + store + .delete( + StoredProviderCredentialRefreshState::object_type(), + "original-id", + ) + .await + .unwrap(); + let mut replacement = original; + replacement.metadata.as_mut().unwrap().id = "replacement-id".into(); + store.put_message(&replacement).await.unwrap(); + let replacement = store + .get_message::("replacement-id") + .await + .unwrap() + .unwrap(); + + delete_observed_refresh_state_with_credentials(&store, &test_credentials(), observed) + .await + .unwrap(); + assert_eq!( + store + .get_message::("replacement-id") + .await + .unwrap() + .unwrap(), + replacement + ); + } + #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn rotation_does_not_resurrect_refresh_deleted_mid_flight() { let mock_server = MockServer::start().await; diff --git a/crates/openshell-server/src/storage_proto.rs b/crates/openshell-server/src/storage_proto.rs index afe4b3d95d..e15eed5c4d 100644 --- a/crates/openshell-server/src/storage_proto.rs +++ b/crates/openshell-server/src/storage_proto.rs @@ -118,7 +118,7 @@ mod tests { const STORAGE_V1_SCHEMA_SHA256: &str = "574bf5fcff731bd6e3fd84ed3f124161035bd236ef0fb7e32b4d8a8c55ceba5e"; const PUBLIC_RPC_SCHEMA_SHA256: &str = - "3c2ad1ef3f38b9bfe029252974e261fb9adf440cebc60acf4b2ff5088f7ba6aa"; + "d323fdd0c989049950ed3bf9cc2cbae9f05e1ae83b13fa2a65a2f5db1ebab95f"; const DURABLE_SCHEMA_SHA256: &str = "65066c0b0eef57a4c708f20fcbbb8e8f47376da9f4bf73dfc3bca0b3df174ba8"; const PUBLIC_DURABLE_OVERLAP_SHA256: &str = @@ -409,6 +409,51 @@ mod tests { } } + #[test] + fn deletion_responses_reserve_legacy_booleans() { + let public = FileDescriptorSet::decode(openshell_core::FILE_DESCRIPTOR_SET).unwrap(); + for (name, legacy) in [ + ("DeleteSandboxTemplateResponse", "deleted"), + ("DeleteSandboxResponse", "deleted"), + ("DeleteServiceResponse", "deleted"), + ("DeleteProviderResponse", "deleted"), + ("DeleteProviderRefreshResponse", "deleted"), + ("DeleteProviderProfileResponse", "deleted"), + ("DeleteWorkspaceResponse", "deleted"), + ("RemoveWorkspaceMemberResponse", "removed"), + ("RevokeSshSessionResponse", "revoked"), + ] { + let message = public + .file + .iter() + .filter(|file| file.package.as_deref() == Some("openshell.v1")) + .flat_map(|file| &file.message_type) + .find(|message| message.name.as_deref() == Some(name)) + .unwrap(); + assert!(message.reserved_name.iter().any(|name| name == legacy)); + assert!( + message + .reserved_range + .iter() + .any(|range| range.start == Some(1) && range.end == Some(2)) + ); + let outcome = message + .field + .iter() + .find(|field| field.name.as_deref() == Some("outcome")) + .unwrap(); + assert_eq!(outcome.number, Some(2)); + assert_eq!( + outcome.type_name.as_deref(), + Some(".openshell.v1.DeletionOutcome") + ); + } + let legacy = openshell_core::proto::DeleteSandboxResponse::decode(&[8, 1][..]).unwrap(); + assert_eq!(legacy.outcome, 0); + let unknown = openshell_core::proto::DeleteSandboxResponse::decode(&[16, 99][..]).unwrap(); + assert_eq!(unknown.outcome, 99); + } + #[test] fn public_and_durable_schema_inventories_are_complete() { let public = FileDescriptorSet::decode(openshell_core::FILE_DESCRIPTOR_SET) @@ -490,7 +535,7 @@ mod tests { assert_eq!( (public_closure.messages.len(), public_closure.enums.len()), - (283, 13) + (283, 14) ); assert_eq!( (durable_closure.messages.len(), durable_closure.enums.len()), diff --git a/crates/openshell-server/tests/common/mod.rs b/crates/openshell-server/tests/common/mod.rs index fe159dcf64..b005b53300 100644 --- a/crates/openshell-server/tests/common/mod.rs +++ b/crates/openshell-server/tests/common/mod.rs @@ -201,7 +201,10 @@ impl OpenShell for TestOpenShell { &self, _request: tonic::Request, ) -> Result, Status> { - Ok(Response::new(DeleteSandboxResponse { deleted: true })) + Ok(Response::new(DeleteSandboxResponse { + sandbox_id: String::new(), + outcome: openshell_core::proto::DeletionOutcome::Completed.into(), + })) } async fn get_sandbox_config( diff --git a/crates/openshell-tui/src/lib.rs b/crates/openshell-tui/src/lib.rs index 17eb0ad18e..3d451e13c8 100644 --- a/crates/openshell-tui/src/lib.rs +++ b/crates/openshell-tui/src/lib.rs @@ -819,11 +819,21 @@ async fn handle_sandbox_delete(app: &mut App, tx: mpsc::UnboundedSender) } let req = openshell_core::proto::DeleteSandboxRequest { + allow_missing: true, name: sandbox_name, workspace_scope: Some(named_workspace_scope(app.selected_sandbox_workspace())), }; match app.client.delete_sandbox(req).await { - Ok(_) => { + Ok(response) => { + use openshell_core::proto::DeletionOutcome; + app.status_text = match response.into_inner().outcome() { + DeletionOutcome::Completed => "sandbox deleted".into(), + DeletionOutcome::Accepted => "sandbox deletion accepted; cleanup is pending".into(), + DeletionOutcome::AlreadyAbsent => "sandbox already deleted".into(), + DeletionOutcome::Unspecified => { + "delete failed: unsupported deletion outcome".into() + } + }; app.cancel_log_stream(); app.screen = Screen::Dashboard; app.focus = Focus::Sandboxes; @@ -1909,12 +1919,21 @@ fn spawn_delete_provider(app: &App, tx: mpsc::UnboundedSender) { tokio::spawn(async move { let req = openshell_core::proto::DeleteProviderRequest { + allow_missing: true, name, workspace_scope: Some(named_workspace_scope(workspace)), }; match tokio::time::timeout(Duration::from_secs(5), client.delete_provider(req)).await { Ok(Ok(resp)) => { - let _ = tx.send(Event::ProviderDeleteResult(Ok(resp.into_inner().deleted))); + let outcome = resp.into_inner().outcome(); + let result = match outcome { + openshell_core::proto::DeletionOutcome::Completed => Ok(true), + openshell_core::proto::DeletionOutcome::AlreadyAbsent => Ok(false), + _ => { + Err("gateway returned an unsupported provider deletion outcome".to_string()) + } + }; + let _ = tx.send(Event::ProviderDeleteResult(result)); } Ok(Err(e)) => { let _ = tx.send(Event::ProviderDeleteResult(Err(e.message().to_string()))); diff --git a/docs/reference/api-errors.mdx b/docs/reference/api-errors.mdx index 4fec785a68..9f77169ab0 100644 --- a/docs/reference/api-errors.mdx +++ b/docs/reference/api-errors.mdx @@ -76,10 +76,85 @@ Existing error codes and human-readable messages remain available. Python curate clients now raise `GatewayError`, which remains a `grpc.RpcError`; existing `except grpc.RpcError` handlers continue to work. `GatewayError` is not a `grpc.Call`. If your handler checks that interface, inspect `raw_error` before -checking the status. The Python SDK's deletion wait and managed-sandbox cleanup -use the original call to recognize `NOT_FOUND`; other failures still propagate. -Rust error variants now retain +checking the status, or call `GatewayError.code()` directly. Python deletion waits +recognize `NOT_FOUND` through the wrapper; managed-sandbox cleanup requests +`allow_missing=True` and handles the typed deletion outcome. Other failures still +propagate. Rust error variants now retain status fields; use `..` when destructuring variants that do not need those fields. Go and TypeScript add typed detail fields to their existing error types. No SDK automatically retries a mutation as a result of decoding these details. + +## Deletion outcomes + +Delete, membership-removal, and SSH-revocation RPCs return a typed outcome. +Transport success alone does not establish completion. + +| Outcome | Meaning | +|---|---| +| `COMPLETED` | The gateway resource was removed, or the existing SSH session is revoked. Downstream platform garbage collection may still be finishing. | +| `ACCEPTED` | Sandbox deletion started, but the gateway sandbox record still exists. Observe its removal before assuming completion. | +| `ALREADY_ABSENT` | The target was missing when resolved, and the request set `allow_missing=true`. | +| `UNSPECIFIED` or an unknown value | Completion is not established. Check gateway and SDK compatibility. | + +Requests default to `allow_missing=false`: an initially missing target returns +`NOT_FOUND`. Set it to `true` for cleanup that allows an absent target. This flag +does not suppress missing parents, authorization failures, invalid requests, +failed preconditions, or backend errors. Re-revoking an existing revoked session +returns `COMPLETED` after authorization without changing its resource version. + +`DeleteSandbox` also returns the original `sandbox_id` when it found a target. +For an accepted deletion, poll or watch that identity. A same-name sandbox with a +different ID is not the sandbox being deleted. Cancellation or a disconnected +client does not stop the gateway's already-started deletion worker. + +Pass the deletion result's ID to the SDK wait helper to avoid following a +same-name replacement. + +| SDK | Identity-aware deletion wait | +|---|---| +| Rust | `client.wait_deleted(name, timeout, result.sandbox_id.as_deref()).await?` on either the default or workspace-scoped client. | +| Python | `client.wait_deleted(name, workspace=workspace, expected_sandbox_id=result.sandbox_id)`. Managed-sandbox cleanup supplies this automatically. | +| TypeScript | `client.sandbox.waitDeleted(name, timeoutSecs, { workspace, expectedSandboxId: result.sandboxId })`. | + +These waits complete on `NOT_FOUND` or a different observed ID. Without an +expected ID, they wait for the name to be absent. Existing Rust callers must add +the third argument; use `None` to retain name-only behavior. + +`allow_missing` is not request deduplication. A later request by name can delete +a newly created resource with that name. Do not blindly repeat a timed-out +deletion when names might be reused. + +### SDK migration for deletion + +Upgrade gateways and clients together. This pre-1.0 change removes the boolean +response fields, reserving their names and wire numbers. New clients reading an +old response see `UNSPECIFIED`; they must not interpret it as completion. + +| RPC | Removed field | Replacement | +|---|---|---| +| `DeleteSandboxTemplate` | `deleted` | `outcome` | +| `DeleteSandbox` | `deleted` | `outcome`, `sandbox_id` | +| `DeleteService` | `deleted` | `outcome` | +| `DeleteProvider` | `deleted` | `outcome` | +| `DeleteProviderRefresh` | `deleted` | `outcome` | +| `DeleteProviderProfile` | `deleted` | `outcome` | +| `DeleteWorkspace` | `deleted` | `outcome` | +| `RemoveWorkspaceMember` | `removed` | `outcome` | +| `RevokeSshSession` | `revoked` | `outcome` | + +These RPCs use a typed result rather than `Empty` because callers must +distinguish absence, asynchronous acceptance, and completion. + +Curated SDK methods return `DeletionResult` instead of a boolean or no result. +Rust accepts `DeleteOptions { allow_missing: true }`; Python accepts the keyword +`allow_missing=True`; TypeScript accepts `{ allowMissing: true }`; Go accepts an +optional `DeleteOptions{AllowMissing: true}` and returns `(result, error)`. +Go and Python preserve unknown numeric outcomes, Rust uses `Unknown(i32)`, and +TypeScript returns `outcome: 'unknown'` with `rawOutcome`. + +Other mutation responses remain unchanged: provider attach/detach also return +the resulting sandbox, profile import/update return resources and diagnostics, +and config updates return version/application information. Their supplementary +booleans are not the sole result. Internal compute-driver and supervisor +contracts are separate from this public API migration. diff --git a/docs/sandboxes/manage-sandboxes.mdx b/docs/sandboxes/manage-sandboxes.mdx index 380d58cda5..5415d993b0 100644 --- a/docs/sandboxes/manage-sandboxes.mdx +++ b/docs/sandboxes/manage-sandboxes.mdx @@ -736,6 +736,12 @@ longer need its retained state. Deleting a sandbox stops all processes, releases resources, and purges injected credentials. +The command can return while cleanup is pending. `deletion accepted` means the +gateway started deletion; inspect the sandbox until it disappears if your next +step requires completion. An already-absent sandbox is a successful no-op, but +missing workspaces and authorization failures remain errors. SDK callers can +inspect [typed deletion outcomes](/reference/api-errors#deletion-outcomes). + ```shell openshell sandbox delete my-sandbox ``` diff --git a/e2e/rust/tests/sandbox_lifecycle.rs b/e2e/rust/tests/sandbox_lifecycle.rs index 5ba9a67674..389fb72f0f 100644 --- a/e2e/rust/tests/sandbox_lifecycle.rs +++ b/e2e/rust/tests/sandbox_lifecycle.rs @@ -302,9 +302,15 @@ async fn sandbox_can_be_deleted_while_stopped() { ); let delete_output = run_sandbox_lifecycle_command("delete", &sandbox.name).await; + // Deletion may return before the owned cleanup worker finishes. Both + // outcomes must still reach absence, which is checked below. assert!( - delete_output.contains("Deleted sandbox"), - "expected delete confirmation in:\n{delete_output}", + delete_output.contains(&format!("Deleted sandbox {}", sandbox.name)) + || delete_output.contains(&format!( + "Sandbox {} deletion accepted; cleanup is pending", + sandbox.name + )), + "expected completed or accepted deletion in:\n{delete_output}", ); if let Err(last_sandbox_list) = assert_sandbox_presence_eventually(&sandbox.name, false).await { diff --git a/proto/openshell.proto b/proto/openshell.proto index 33e3d0ce85..a349e1db8a 100644 --- a/proto/openshell.proto +++ b/proto/openshell.proto @@ -1200,6 +1200,9 @@ message DeleteSandboxTemplateRequest { string name = 1; // Explicit workspace scope. The all-workspaces selection is invalid. openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3; + // Succeed with ALREADY_ABSENT if the target is missing. Authorization and + // parent-workspace checks still apply. + bool allow_missing = 4; } message SandboxTemplateResponse { @@ -1213,7 +1216,9 @@ message ListSandboxTemplatesResponse { } message DeleteSandboxTemplateResponse { - bool deleted = 1; + reserved 1; + reserved "deleted"; + DeletionOutcome outcome = 2; } // Request a gateway-owned staging slot for a local rootfs tar archive. @@ -1325,6 +1330,9 @@ message DeleteSandboxRequest { string name = 1; // Explicit workspace scope. The all-workspaces selection is invalid. openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3; + // Succeed with ALREADY_ABSENT if the target is missing. Does not wait for + // asynchronous cleanup and does not suppress authorization or parent errors. + bool allow_missing = 4; } // Stop sandbox request. @@ -1380,7 +1388,12 @@ message DetachSandboxProviderResponse { // Delete sandbox response. message DeleteSandboxResponse { - bool deleted = 1; + reserved 1; + reserved "deleted"; + DeletionOutcome outcome = 2; + // Immutable identity of the targeted sandbox, empty for ALREADY_ABSENT. + // A same-name replacement is not part of this deletion. + string sandbox_id = 3; } // Create SSH session request. @@ -1486,12 +1499,14 @@ message DeleteServiceRequest { string service = 2; // Explicit workspace scope. The all-workspaces selection is invalid. openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4; + bool allow_missing = 5; } // Response for deleting an exposed sandbox service endpoint. message DeleteServiceResponse { - // True when an endpoint existed and was deleted. - bool deleted = 1; + reserved 1; + reserved "deleted"; + DeletionOutcome outcome = 2; } // Persisted sandbox service endpoint. @@ -1520,12 +1535,16 @@ message ServiceEndpointResponse { message RevokeSshSessionRequest { // Session token to revoke. string token = 1 [(openshell.options.v1.secret) = true]; + // A missing token is NOT_FOUND unless this is true. Revoking an existing, + // already-revoked session succeeds with COMPLETED. + bool allow_missing = 2; } // Revoke SSH session response. message RevokeSshSessionResponse { - // True when a session was revoked. - bool revoked = 1; + reserved 1; + reserved "revoked"; + DeletionOutcome outcome = 2; } // Execute command request. @@ -1783,6 +1802,7 @@ message DeleteProviderRequest { string name = 1; // Explicit workspace scope. The all-workspaces selection is invalid. openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3; + bool allow_missing = 4; } // Provider response. @@ -2050,10 +2070,13 @@ message DeleteProviderRefreshRequest { string credential_key = 2; // Explicit workspace scope. The all-workspaces selection is invalid. openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4; + bool allow_missing = 5; } message DeleteProviderRefreshResponse { - bool deleted = 1; + reserved 1; + reserved "deleted"; + DeletionOutcome outcome = 2; } // Stable provider profile categories used by clients for grouping and filtering. @@ -2158,7 +2181,9 @@ message LintProviderProfilesResponse { // Delete provider response. message DeleteProviderResponse { - bool deleted = 1; + reserved 1; + reserved "deleted"; + DeletionOutcome outcome = 2; } // Delete custom provider profile request. @@ -2167,11 +2192,14 @@ message DeleteProviderProfileRequest { // Workspace scope. When set, targets workspace-scoped profile. When empty, // targets platform-scoped profile. string workspace = 2; + bool allow_missing = 3; } // Delete custom provider profile response. message DeleteProviderProfileResponse { - bool deleted = 1; + reserved 1; + reserved "deleted"; + DeletionOutcome outcome = 2; } // Get sandbox provider environment request. @@ -3070,11 +3098,14 @@ message ListWorkspacesResponse { message DeleteWorkspaceRequest { // Workspace name (canonical lookup key). string name = 1; + bool allow_missing = 2; } // Delete workspace response. message DeleteWorkspaceResponse { - bool deleted = 1; + reserved 1; + reserved "deleted"; + DeletionOutcome outcome = 2; } // --------------------------------------------------------------------------- @@ -3130,11 +3161,32 @@ message RemoveWorkspaceMemberRequest { string workspace = 1; // OIDC subject claim identifying the principal to remove. string principal_subject = 2; + bool allow_missing = 3; } // Remove workspace member response. message RemoveWorkspaceMemberResponse { - bool removed = 1; + reserved 1; + reserved "removed"; + DeletionOutcome outcome = 2; +} + +// Result of a public delete, membership removal, or session revocation. +// Default requests return NOT_FOUND for a missing target. With allow_missing, +// only a missing target becomes ALREADY_ABSENT; parent lookup, authorization, +// validation, precondition, and backend errors retain their normal status. +// These results describe the targeted resource, not a same-name replacement. +enum DeletionOutcome { + // No outcome was supplied. Never infer completion from this value. + DELETION_OUTCOME_UNSPECIFIED = 0; + // The targeted gateway resource is removed (or the SSH session is revoked). + // Downstream platform garbage collection may still be finishing. + DELETION_OUTCOME_COMPLETED = 1; + // Sandbox deletion is accepted but its gateway record still exists. + // Observe the targeted sandbox ID until it disappears for completion. + DELETION_OUTCOME_ACCEPTED = 2; + // The target did not exist and allow_missing was true. + DELETION_OUTCOME_ALREADY_ABSENT = 3; } // List workspace members request. diff --git a/python/openshell/__init__.py b/python/openshell/__init__.py index 072858ca5e..8cfc8cb338 100644 --- a/python/openshell/__init__.py +++ b/python/openshell/__init__.py @@ -6,6 +6,7 @@ from __future__ import annotations from .errors import ErrorInfo, FieldViolation, GatewayError, from_grpc_error +from .mutations import DeletionOutcome, DeletionResult from .sandbox import ( ClientCredentialsAuth, ExecChunk, @@ -34,6 +35,8 @@ __all__ = [ "ClientCredentialsAuth", + "DeletionOutcome", + "DeletionResult", "ErrorInfo", "ExecChunk", "ExecResult", diff --git a/python/openshell/mutations.py b/python/openshell/mutations.py new file mode 100644 index 0000000000..efa538c95a --- /dev/null +++ b/python/openshell/mutations.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Typed mutation results, separate from transport success.""" + +from dataclasses import dataclass +from enum import IntEnum + + +class DeletionOutcome(IntEnum): + """Only COMPLETED and ALREADY_ABSENT establish logical deletion completion.""" + + UNSPECIFIED = 0 + COMPLETED = 1 + ACCEPTED = 2 + ALREADY_ABSENT = 3 + + @classmethod + def _missing_(cls, value): + if not isinstance(value, int): + return None + member = int.__new__(cls, value) + member._name_ = f"UNKNOWN_{value}" + member._value_ = value + return member + + +@dataclass(frozen=True) +class DeletionResult: + """Outcome for the original target; unknown outcomes never imply completion.""" + + outcome: DeletionOutcome + sandbox_id: str | None = None diff --git a/python/openshell/sandbox.py b/python/openshell/sandbox.py index ba69a0a695..7dbf1b9d49 100644 --- a/python/openshell/sandbox.py +++ b/python/openshell/sandbox.py @@ -28,7 +28,8 @@ openshell_pb2, openshell_pb2_grpc, ) -from .errors import GatewayError, _error_mapping_channel +from .errors import _error_mapping_channel +from .mutations import DeletionOutcome, DeletionResult _ClientCallDetailsBase = namedtuple( "_ClientCallDetailsBase", @@ -534,8 +535,10 @@ def exec_python( timeout_seconds=timeout_seconds, ) - def delete(self) -> bool: - return self._client.delete(self.sandbox.name, workspace=self._workspace) + def delete(self, *, allow_missing: bool = False) -> DeletionResult: + return self._client.delete( + self.sandbox.name, workspace=self._workspace, allow_missing=allow_missing + ) def stop(self) -> SandboxRef: self.sandbox = self._client.stop(self.sandbox.name, workspace=self._workspace) @@ -956,14 +959,20 @@ def list_ids_for_all_workspaces( ) ] - def delete(self, sandbox_name: str, *, workspace: str) -> bool: + def delete( + self, sandbox_name: str, *, workspace: str, allow_missing: bool = False + ) -> DeletionResult: response = self._stub.DeleteSandbox( openshell_pb2.DeleteSandboxRequest( - name=sandbox_name, workspace_scope=_workspace_scope(workspace) + name=sandbox_name, + workspace_scope=_workspace_scope(workspace), + allow_missing=allow_missing, ), timeout=self._timeout, ) - return bool(response.deleted) + return DeletionResult( + DeletionOutcome(response.outcome), response.sandbox_id or None + ) def stop(self, sandbox_name: str, *, workspace: str) -> SandboxRef: response = self._stub.StopSandbox( @@ -984,19 +993,25 @@ def start(self, sandbox_name: str, *, workspace: str) -> SandboxRef: return _sandbox_ref(response.sandbox) def wait_deleted( - self, sandbox_name: str, *, workspace: str, timeout_seconds: float = 60.0 + self, + sandbox_name: str, + *, + workspace: str, + timeout_seconds: float = 60.0, + expected_sandbox_id: str | None = None, ) -> None: deadline = time.time() + timeout_seconds while time.time() < deadline: try: - self.get(sandbox_name, workspace=workspace) - except grpc.RpcError as exc: - call = exc.raw_error if isinstance(exc, GatewayError) else exc + current = self.get(sandbox_name, workspace=workspace) if ( - isinstance(call, grpc.Call) - and call.code() == grpc.StatusCode.NOT_FOUND + expected_sandbox_id is not None + and current.id != expected_sandbox_id ): return + except grpc.RpcError as exc: + if getattr(exc, "code", lambda: None)() == grpc.StatusCode.NOT_FOUND: + return raise time.sleep(1) raise SandboxError(f"sandbox {sandbox_name} was not deleted within timeout") @@ -1330,14 +1345,18 @@ def list_all_for_all_workspaces( label_selector=label_selector, ).all() - def delete(self, name: str, *, workspace: str) -> bool: + def delete( + self, name: str, *, workspace: str, allow_missing: bool = False + ) -> DeletionResult: response = self._stub.DeleteSandboxTemplate( openshell_pb2.DeleteSandboxTemplateRequest( - name=name, workspace_scope=_workspace_scope(workspace) + name=name, + workspace_scope=_workspace_scope(workspace), + allow_missing=allow_missing, ), timeout=self._timeout, ) - return bool(response.deleted) + return DeletionResult(DeletionOutcome(response.outcome)) @dataclass(frozen=True) @@ -1425,12 +1444,14 @@ def list_all( label_selector=label_selector, ).all() - def delete(self, name: str) -> bool: + def delete(self, name: str, *, allow_missing: bool = False) -> DeletionResult: response = self._stub.DeleteWorkspace( - openshell_pb2.DeleteWorkspaceRequest(name=name), + openshell_pb2.DeleteWorkspaceRequest( + name=name, allow_missing=allow_missing + ), timeout=self._timeout, ) - return response.deleted + return DeletionResult(DeletionOutcome(response.outcome)) class Sandbox: @@ -1556,20 +1577,20 @@ def __exit__(self, *args: object) -> None: and self._session is not None and self._client is not None ): - try: - deleted = self._session.delete() - if deleted: - self._client.wait_deleted( - self._session.sandbox.name, - workspace=self._workspace, - ) - except grpc.RpcError as exc: - call = exc.raw_error if isinstance(exc, GatewayError) else exc - if ( - not isinstance(call, grpc.Call) - or call.code() != grpc.StatusCode.NOT_FOUND - ): - raise + result = self._session.delete(allow_missing=True) + if result.outcome == DeletionOutcome.ACCEPTED: + self._client.wait_deleted( + self._session.sandbox.name, + workspace=self._workspace, + expected_sandbox_id=result.sandbox_id, + ) + elif result.outcome not in ( + DeletionOutcome.COMPLETED, + DeletionOutcome.ALREADY_ABSENT, + ): + raise SandboxError( + f"unsupported deletion outcome: {result.outcome}" + ) finally: if self._client is not None: self._client.close() diff --git a/python/openshell/sandbox_cleanup_test.py b/python/openshell/sandbox_cleanup_test.py index 903fa1131a..7d38fadf36 100644 --- a/python/openshell/sandbox_cleanup_test.py +++ b/python/openshell/sandbox_cleanup_test.py @@ -45,6 +45,11 @@ def delete(request, context): state.calls.append("DeleteSandbox") assert request.name == "cleanup-test" assert request.workspace_scope.workspace == "default" + assert request.allow_missing + if state.code == grpc.StatusCode.NOT_FOUND: + return openshell_pb2.DeleteSandboxResponse( + outcome=openshell_pb2.DELETION_OUTCOME_ALREADY_ABSENT + ) fail(context) server = grpc.server(futures.ThreadPoolExecutor(max_workers=1)) @@ -125,7 +130,9 @@ def test_wait_deleted_handles_intercepted_status(cleanup_client, code): grpc.StatusCode.UNAVAILABLE, ], ) -def test_context_cleanup_handles_intercepted_status(cleanup_client, monkeypatch, code): +def test_context_cleanup_handles_absence_and_intercepted_errors( + cleanup_client, monkeypatch, code +): client, state = cleanup_client state.code = code state.exists = True diff --git a/python/openshell/sandbox_test.py b/python/openshell/sandbox_test.py index 0675bc7338..4b8e60e07d 100644 --- a/python/openshell/sandbox_test.py +++ b/python/openshell/sandbox_test.py @@ -18,6 +18,7 @@ import openshell.sandbox as sandbox_module from openshell._proto import openshell_pb2 +from openshell.mutations import DeletionOutcome from openshell.sandbox import ( _OIDC_TOKEN_EXPIRY_GRACE_SECONDS, _PYTHON_CLOUDPICKLE_BOOTSTRAP, @@ -2013,7 +2014,7 @@ def DeleteSandbox( ) -> Any: self.delete_request = request _ = timeout - return SimpleNamespace(deleted=True) + return SimpleNamespace(outcome=1, sandbox_id="sb-1") def StopSandbox( self, @@ -2122,7 +2123,7 @@ def DeleteSandboxTemplate( ) -> Any: self.delete_template_request = request _ = timeout - return SimpleNamespace(deleted=True) + return SimpleNamespace(outcome=1, sandbox_id="sb-1") class _RecordingHighLevelClient: @@ -2421,7 +2422,7 @@ def test_sandbox_template_client_crud_forwards_requests() -> None: assert stub.list_template_request.label_selector == "team=runtime" assert not _request_selects_all_workspaces(stub.list_template_request) - assert client.delete("gpu-kata", workspace="default") is True + assert client.delete("gpu-kata", workspace="default").outcome == 1 assert stub.delete_template_request is not None assert stub.delete_template_request.name == "gpu-kata" assert _request_workspace(stub.delete_template_request) == "default" @@ -2835,9 +2836,32 @@ def test_delete_passes_workspace_to_proto() -> None: result = client.delete("job-1", workspace="staging") - assert result is True + assert result.outcome == 1 assert stub.delete_request is not None assert _request_workspace(stub.delete_request) == "staging" + assert not stub.delete_request.allow_missing + + +@pytest.mark.parametrize("outcome", [0, 1, 2, 3, 99]) +def test_delete_preserves_outcome_and_identity(outcome: int) -> None: + class Stub: + def DeleteSandbox(self, request: Any, **_kwargs: Any) -> Any: + assert request.allow_missing + return openshell_pb2.DeleteSandboxResponse( + outcome=cast("openshell_pb2.DeletionOutcome", outcome), + sandbox_id="original-id", + ) + + result = _client_with_fake_stub(Stub()).delete( + "job", workspace="default", allow_missing=True + ) + assert int(result.outcome) == outcome + assert result.sandbox_id == "original-id" + if outcome == 99: + assert result.outcome not in ( + DeletionOutcome.COMPLETED, + DeletionOutcome.ALREADY_ABSENT, + ) def test_list_for_all_workspaces_sets_flag() -> None: diff --git a/sdk/go/docs/src/api/fake.md b/sdk/go/docs/src/api/fake.md index fd1e14bbfa..7ddc381992 100644 --- a/sdk/go/docs/src/api/fake.md +++ b/sdk/go/docs/src/api/fake.md @@ -27,7 +27,9 @@ func TestSandboxLifecycle(t *testing.T) { require.NoError(t, err) assert.Equal(t, types.SandboxReady, sb.Status.Phase) - require.NoError(t, client.Sandboxes().Delete(ctx, "default", "my-sandbox")) + deletion, err := client.Sandboxes().Delete(ctx, "default", "my-sandbox") + require.NoError(t, err) + assert.Equal(t, v1.DeletionCompleted, deletion.Outcome) } ``` diff --git a/sdk/go/docs/src/api/providers.md b/sdk/go/docs/src/api/providers.md index 4354843ae0..5ce6b98551 100644 --- a/sdk/go/docs/src/api/providers.md +++ b/sdk/go/docs/src/api/providers.md @@ -89,7 +89,7 @@ fmt.Println("Updated provider:", updated.Name) Remove a provider by name. ```go -err := client.Providers().Delete(ctx, "default", "my-openai") +deletion, err := client.Providers().Delete(ctx, "default", "my-openai") if err != nil { log.Fatal(err) } diff --git a/sdk/go/docs/src/api/sandbox-templates.md b/sdk/go/docs/src/api/sandbox-templates.md index 764014400c..50f337150b 100644 --- a/sdk/go/docs/src/api/sandbox-templates.md +++ b/sdk/go/docs/src/api/sandbox-templates.md @@ -108,7 +108,7 @@ Deletes a template by name. Existing sandboxes created from the template are not deleted. ```go -deleted, err := client.SandboxTemplates().Delete(ctx, "default", "gpu-kata") +deletion, err := client.SandboxTemplates().Delete(ctx, "default", "gpu-kata") ``` ## Fake Client diff --git a/sdk/go/docs/src/api/sandboxes.md b/sdk/go/docs/src/api/sandboxes.md index 7fb1ed755b..f9504227c2 100644 --- a/sdk/go/docs/src/api/sandboxes.md +++ b/sdk/go/docs/src/api/sandboxes.md @@ -101,9 +101,16 @@ allSandboxes, err := client.Sandboxes().ListAll(ctx, "", v1.ListOptions{ Deletes a sandbox by name. ```go -err := client.Sandboxes().Delete(ctx, "default", "my-sandbox") +deletion, err := client.Sandboxes().Delete(ctx, "default", "my-sandbox", v1.DeleteOptions{AllowMissing: true}) ``` +Missing targets return `NotFound` unless `AllowMissing` is true. Inspect +`deletion.Outcome`: `DeletionAccepted` means cleanup is pending, while +`DeletionCompleted` and `DeletionAlreadyAbsent` establish logical completion. +Unknown values do not establish completion. `deletion.SandboxID` identifies the +original sandbox; do not confuse a same-name replacement with that target. +Allowing absence does not make a retry safe if names can be reused. + ## AttachProvider Attaches a provider to a sandbox. The `expectedResourceVersion` enables optimistic concurrency control: pass the sandbox's current `ResourceVersion` to ensure no other client has modified it since your last read. diff --git a/sdk/go/docs/src/api/services.md b/sdk/go/docs/src/api/services.md index d309f7975f..0bc542f28c 100644 --- a/sdk/go/docs/src/api/services.md +++ b/sdk/go/docs/src/api/services.md @@ -44,7 +44,7 @@ Remove an exposed service. The underlying sandbox port remains accessible internally but is no longer reachable through the service endpoint. ```go -err := client.Services().Delete(ctx, "default", "my-sandbox", "web") +deletion, err := client.Services().Delete(ctx, "default", "my-sandbox", "web") if err != nil { log.Fatal(err) } diff --git a/sdk/go/docs/src/api/ssh.md b/sdk/go/docs/src/api/ssh.md index 29f2a509e9..dfe03cfbb5 100644 --- a/sdk/go/docs/src/api/ssh.md +++ b/sdk/go/docs/src/api/ssh.md @@ -23,11 +23,11 @@ fmt.Printf("SSH via %s://%s:%d\n", session.GatewayScheme, session.GatewayHost, s Revoke an active SSH session, immediately terminating any connections using it. ```go -revoked, err := client.SSH().RevokeSession(ctx, session.Token) +deletion, err := client.SSH().RevokeSession(ctx, "default", session.Token) if err != nil { log.Fatal(err) } -if revoked { +if deletion.Outcome == v1.DeletionCompleted { fmt.Println("Session revoked") } ``` diff --git a/sdk/go/docs/src/getting-started.md b/sdk/go/docs/src/getting-started.md index 31530c914c..c3cc224990 100644 --- a/sdk/go/docs/src/getting-started.md +++ b/sdk/go/docs/src/getting-started.md @@ -107,11 +107,11 @@ For long-running commands, use `Stream` to receive output incrementally, or `Int Delete the sandbox when you are done: ```go - err = client.Sandboxes().Delete(ctx, "default", sandbox.Name) + deletion, err := client.Sandboxes().Delete(ctx, "default", sandbox.Name) if err != nil { log.Fatal(err) } - fmt.Println("Sandbox deleted") + fmt.Printf("Deletion outcome: %v\n", deletion.Outcome) } ``` diff --git a/sdk/go/docs/src/testing.md b/sdk/go/docs/src/testing.md index 138738ab19..1e6e693246 100644 --- a/sdk/go/docs/src/testing.md +++ b/sdk/go/docs/src/testing.md @@ -87,7 +87,7 @@ sb, err = client.Sandboxes().WaitReady(ctx, "default", "my-sandbox") assert.Equal(t, types.SandboxReady, sb.Status.Phase) // Delete removes the sandbox -err = client.Sandboxes().Delete(ctx, "default", "my-sandbox") +_, err = client.Sandboxes().Delete(ctx, "default", "my-sandbox") assert.NoError(t, err) // Get after delete returns NotFound diff --git a/sdk/go/openshell/v1/doc.go b/sdk/go/openshell/v1/doc.go index ecf995dd43..c600ac382d 100644 --- a/sdk/go/openshell/v1/doc.go +++ b/sdk/go/openshell/v1/doc.go @@ -230,11 +230,11 @@ // fmt.Printf("Host key: %s\n", session.HostKeyFingerprint) // // Use session.Token to authenticate the SSH connection. // -// revoked, err := client.SSH().RevokeSession(ctx, "default", session.Token) +// deletion, err := client.SSH().RevokeSession(ctx, "default", session.Token) // if err != nil { // log.Fatal(err) // } -// fmt.Printf("Session revoked: %v\n", revoked) +// fmt.Printf("Revocation outcome: %v\n", deletion.Outcome) // // # TCP Port Forwarding // diff --git a/sdk/go/openshell/v1/example_test.go b/sdk/go/openshell/v1/example_test.go index d0f6e115c7..c69e7816e1 100644 --- a/sdk/go/openshell/v1/example_test.go +++ b/sdk/go/openshell/v1/example_test.go @@ -35,7 +35,7 @@ func ExampleClient_Sandboxes() { fmt.Println("Phase after wait:", sb.Status.Phase) // Clean up - if err := client.Sandboxes().Delete(ctx, "default", "my-sandbox"); err != nil { + if _, err := client.Sandboxes().Delete(ctx, "default", "my-sandbox"); err != nil { log.Fatal(err) } fmt.Println("Deleted") diff --git a/sdk/go/openshell/v1/exec_client_test.go b/sdk/go/openshell/v1/exec_client_test.go index 183940ec8b..e82550e9bb 100644 --- a/sdk/go/openshell/v1/exec_client_test.go +++ b/sdk/go/openshell/v1/exec_client_test.go @@ -43,7 +43,7 @@ func (r *stubSandboxResolver) List(string, ...ListOptions) (*Pager[*Sandbox], er func (r *stubSandboxResolver) ListAll(context.Context, string, ...ListOptions) ([]*Sandbox, error) { panic("not implemented") } -func (r *stubSandboxResolver) Delete(context.Context, string, string) error { +func (r *stubSandboxResolver) Delete(context.Context, string, string, ...DeleteOptions) (*DeletionResult, error) { panic("not implemented") } func (r *stubSandboxResolver) AttachProvider(context.Context, string, string, string, uint64) (*AttachProviderResult, error) { diff --git a/sdk/go/openshell/v1/fake/mutations.go b/sdk/go/openshell/v1/fake/mutations.go new file mode 100644 index 0000000000..2f69ccf326 --- /dev/null +++ b/sdk/go/openshell/v1/fake/mutations.go @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package fake + +import ( + v1 "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1" + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +func deletionResult(existed bool, sandboxID string, opts []v1.DeleteOptions) (*types.DeletionResult, error) { + if existed { + return &types.DeletionResult{Outcome: types.DeletionCompleted, SandboxID: sandboxID}, nil + } + if len(opts) > 0 && opts[0].AllowMissing { + return &types.DeletionResult{Outcome: types.DeletionAlreadyAbsent}, nil + } + return nil, &types.StatusError{Code: types.ErrorNotFound, Message: "target not found"} +} diff --git a/sdk/go/openshell/v1/fake/profile.go b/sdk/go/openshell/v1/fake/profile.go index b8298ee00d..d39d65dbcd 100644 --- a/sdk/go/openshell/v1/fake/profile.go +++ b/sdk/go/openshell/v1/fake/profile.go @@ -70,11 +70,11 @@ func (c *fakeProfileClient) Lint(_ context.Context, _ string, _ []types.ProfileI } // Delete returns Unimplemented. -func (c *fakeProfileClient) Delete(_ context.Context, _, _ string) (bool, error) { +func (c *fakeProfileClient) Delete(_ context.Context, _, _ string, _ ...v1.DeleteOptions) (*types.DeletionResult, error) { if c.closedFunc() { - return false, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} + return nil, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} } - return false, &types.StatusError{Code: types.ErrorUnimplemented, Message: "Delete is not supported by the fake client"} + return nil, &types.StatusError{Code: types.ErrorUnimplemented, Message: "Delete is not supported by the fake client"} } // Compile-time check that fakeProfileClient implements v1.ProfileInterface. diff --git a/sdk/go/openshell/v1/fake/provider.go b/sdk/go/openshell/v1/fake/provider.go index 2fd466f054..85512318d2 100644 --- a/sdk/go/openshell/v1/fake/provider.go +++ b/sdk/go/openshell/v1/fake/provider.go @@ -159,12 +159,12 @@ func (c *fakeProviderClient) Update(_ context.Context, workspace string, provide } // Delete removes a provider by name. The operation is idempotent. -func (c *fakeProviderClient) Delete(_ context.Context, workspace, name string) error { +func (c *fakeProviderClient) Delete(_ context.Context, workspace, name string, opts ...v1.DeleteOptions) (*types.DeletionResult, error) { if c.closedFunc() { - return &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} + return nil, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} } - c.store.Delete(workspace, name) - return nil + _, existed := c.store.DeleteAndGet(workspace, name) + return deletionResult(existed, "", opts) } // Ensure creates a provider if it does not exist, or updates it if it does. diff --git a/sdk/go/openshell/v1/fake/provider_test.go b/sdk/go/openshell/v1/fake/provider_test.go index df3d1cb470..c729d063ac 100644 --- a/sdk/go/openshell/v1/fake/provider_test.go +++ b/sdk/go/openshell/v1/fake/provider_test.go @@ -135,7 +135,7 @@ func TestProvider_Delete(t *testing.T) { _, _ = pc.Create(ctx, "default", &types.Provider{Name: "openai"}) - err := pc.Delete(ctx, "default", "openai") + _, err := pc.Delete(ctx, "default", "openai") require.NoError(t, err) _, err = pc.Get(ctx, "default", "openai") @@ -147,7 +147,7 @@ func TestProvider_Delete_Idempotent(t *testing.T) { pc := newTestProviderClient() ctx := context.Background() - err := pc.Delete(ctx, "default", "nonexistent") + _, err := pc.Delete(ctx, "default", "nonexistent", types.DeleteOptions{AllowMissing: true}) require.NoError(t, err) } @@ -268,7 +268,7 @@ func TestProvider_ConcurrentCreateGetListDeleteEnsure(_ *testing.T) { _, _ = pc.ListAll(ctx, "default") _, _ = pc.Update(ctx, "default", &types.Provider{Name: name, Type: "updated"}) _, _ = pc.Ensure(ctx, "default", &types.Provider{Name: name, Type: "ensured"}) - _ = pc.Delete(ctx, "default", name) + _, _ = pc.Delete(ctx, "default", name) } }(i) } diff --git a/sdk/go/openshell/v1/fake/refresh.go b/sdk/go/openshell/v1/fake/refresh.go index 034f2a33df..d7ac38d105 100644 --- a/sdk/go/openshell/v1/fake/refresh.go +++ b/sdk/go/openshell/v1/fake/refresh.go @@ -46,11 +46,11 @@ func (c *fakeRefreshClient) Rotate(_ context.Context, _, _, _ string) (*types.Re } // Delete returns Unimplemented. -func (c *fakeRefreshClient) Delete(_ context.Context, _, _, _ string) (bool, error) { +func (c *fakeRefreshClient) Delete(_ context.Context, _, _, _ string, _ ...v1.DeleteOptions) (*types.DeletionResult, error) { if c.closedFunc() { - return false, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} + return nil, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} } - return false, &types.StatusError{Code: types.ErrorUnimplemented, Message: "Delete is not supported by the fake client"} + return nil, &types.StatusError{Code: types.ErrorUnimplemented, Message: "Delete is not supported by the fake client"} } // Compile-time check that fakeRefreshClient implements v1.RefreshInterface. diff --git a/sdk/go/openshell/v1/fake/sandbox.go b/sdk/go/openshell/v1/fake/sandbox.go index 59c14535b8..0fa43a59d5 100644 --- a/sdk/go/openshell/v1/fake/sandbox.go +++ b/sdk/go/openshell/v1/fake/sandbox.go @@ -575,15 +575,14 @@ func (c *fakeSandboxClient) WaitStopped(ctx context.Context, workspace, name str } // Delete removes a sandbox by name. The operation is idempotent. -func (c *fakeSandboxClient) Delete(_ context.Context, workspace, name string) error { +func (c *fakeSandboxClient) Delete(_ context.Context, workspace, name string, opts ...v1.DeleteOptions) (*types.DeletionResult, error) { if c.closedFunc() { - return &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} + return nil, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} } deleted, existed := c.store.DeleteAndGet(workspace, name) if !existed { - // Not found — idempotent delete - return nil + return deletionResult(false, "", opts) } c.broadcaster.Broadcast(types.Event[*types.Sandbox]{ @@ -591,7 +590,7 @@ func (c *fakeSandboxClient) Delete(_ context.Context, workspace, name string) er Object: deleted, }, name) - return nil + return deletionResult(true, deleted.ID, opts) } // WaitReady transitions a sandbox to the Ready phase. In the fake diff --git a/sdk/go/openshell/v1/fake/sandbox_template.go b/sdk/go/openshell/v1/fake/sandbox_template.go index 438bbf532f..5e560246ac 100644 --- a/sdk/go/openshell/v1/fake/sandbox_template.go +++ b/sdk/go/openshell/v1/fake/sandbox_template.go @@ -138,12 +138,12 @@ func (c *fakeSandboxTemplateClient) ListAll(ctx context.Context, workspace strin return pager.All(ctx) } -func (c *fakeSandboxTemplateClient) Delete(_ context.Context, workspace, name string) (bool, error) { +func (c *fakeSandboxTemplateClient) Delete(_ context.Context, workspace, name string, opts ...v1.DeleteOptions) (*types.DeletionResult, error) { if c.closedFunc() { - return false, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} + return nil, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} } _, existed := c.store.DeleteAndGet(workspace, name) - return existed, nil + return deletionResult(existed, "", opts) } func validateSandboxWorkloadTemplate(template *types.SandboxWorkloadTemplate) error { diff --git a/sdk/go/openshell/v1/fake/sandbox_template_test.go b/sdk/go/openshell/v1/fake/sandbox_template_test.go index 19bf8909b6..3034895055 100644 --- a/sdk/go/openshell/v1/fake/sandbox_template_test.go +++ b/sdk/go/openshell/v1/fake/sandbox_template_test.go @@ -78,14 +78,14 @@ func TestSandboxTemplate_CreateGetListDelete(t *testing.T) { deleted, err := tc.Delete(ctx, "default", "gpu-kata") require.NoError(t, err) - assert.True(t, deleted) + assert.Equal(t, types.DeletionCompleted, deleted.Outcome) _, err = tc.Get(ctx, "default", "gpu-kata") require.Error(t, err) assert.True(t, types.IsNotFound(err)) - deleted, err = tc.Delete(ctx, "default", "gpu-kata") + deleted, err = tc.Delete(ctx, "default", "gpu-kata", types.DeleteOptions{AllowMissing: true}) require.NoError(t, err) - assert.False(t, deleted) + assert.Equal(t, types.DeletionAlreadyAbsent, deleted.Outcome) } func TestSandboxTemplate_CreateAlreadyExists(t *testing.T) { diff --git a/sdk/go/openshell/v1/fake/sandbox_test.go b/sdk/go/openshell/v1/fake/sandbox_test.go index 1a2673808a..c9dcc8829b 100644 --- a/sdk/go/openshell/v1/fake/sandbox_test.go +++ b/sdk/go/openshell/v1/fake/sandbox_test.go @@ -215,7 +215,7 @@ func TestSandbox_Delete(t *testing.T) { _, _ = sc.Create(ctx, "default", "test-sb", &types.SandboxSpec{}, nil) - err := sc.Delete(ctx, "default", "test-sb") + _, err := sc.Delete(ctx, "default", "test-sb") require.NoError(t, err) _, err = sc.Get(ctx, "default", "test-sb") @@ -227,8 +227,8 @@ func TestSandbox_Delete_Idempotent(t *testing.T) { sc := newTestSandboxClient() ctx := context.Background() - // Delete non-existent sandbox should not error - err := sc.Delete(ctx, "default", "nonexistent") + // Explicitly allow a missing target. + _, err := sc.Delete(ctx, "default", "nonexistent", types.DeleteOptions{AllowMissing: true}) require.NoError(t, err) } @@ -417,7 +417,7 @@ func TestSandbox_Watch_DeletedOnDelete(t *testing.T) { require.NoError(t, err) defer w.Stop() - err = sc.Delete(ctx, "default", "test-sb") + _, err = sc.Delete(ctx, "default", "test-sb") require.NoError(t, err) select { @@ -523,7 +523,7 @@ func TestSandbox_Watch_DeletedEventContainsFullObject(t *testing.T) { require.NoError(t, err) defer w.Stop() - _ = sc.Delete(ctx, "default", "test-sb") + _, _ = sc.Delete(ctx, "default", "test-sb") select { case ev := <-w.ResultChan(): @@ -569,7 +569,7 @@ func TestSandbox_ConcurrentCreateGetDeleteWatch(t *testing.T) { _, _ = sc.Get(ctx, "default", name) _, _ = sc.ListAll(ctx, "default") _, _ = sc.WaitReady(ctx, "default", name) - _ = sc.Delete(ctx, "default", name) + _, _ = sc.Delete(ctx, "default", name) } }(i) } diff --git a/sdk/go/openshell/v1/fake/service.go b/sdk/go/openshell/v1/fake/service.go index e3e08c5796..464d8f7fc9 100644 --- a/sdk/go/openshell/v1/fake/service.go +++ b/sdk/go/openshell/v1/fake/service.go @@ -54,11 +54,11 @@ func (c *fakeServiceClient) ListAll(_ context.Context, _, _ string, _ ...v1.List } // Delete returns Unimplemented. -func (c *fakeServiceClient) Delete(_ context.Context, _, _, _ string) error { +func (c *fakeServiceClient) Delete(_ context.Context, _, _, _ string, _ ...v1.DeleteOptions) (*types.DeletionResult, error) { if c.closedFunc() { - return &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} + return nil, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} } - return &types.StatusError{Code: types.ErrorUnimplemented, Message: "Delete is not supported by the fake client"} + return nil, &types.StatusError{Code: types.ErrorUnimplemented, Message: "Delete is not supported by the fake client"} } // Compile-time check that fakeServiceClient implements v1.ServiceInterface. diff --git a/sdk/go/openshell/v1/fake/service_test.go b/sdk/go/openshell/v1/fake/service_test.go index 19e689e07f..052fac33d4 100644 --- a/sdk/go/openshell/v1/fake/service_test.go +++ b/sdk/go/openshell/v1/fake/service_test.go @@ -38,7 +38,7 @@ func TestFakeService_List_ReturnsUnimplemented(t *testing.T) { func TestFakeService_Delete_ReturnsUnimplemented(t *testing.T) { c := newFakeServiceClient(func() bool { return false }) - err := c.Delete(context.Background(), "default", "sb1", "svc1") + _, err := c.Delete(context.Background(), "default", "sb1", "svc1") require.Error(t, err) assert.True(t, types.IsUnimplemented(err)) } @@ -66,7 +66,7 @@ func TestFakeService_List_ClosedReturnsUnavailable(t *testing.T) { func TestFakeService_Delete_ClosedReturnsUnavailable(t *testing.T) { c := newFakeServiceClient(func() bool { return true }) - err := c.Delete(context.Background(), "default", "sb1", "svc1") + _, err := c.Delete(context.Background(), "default", "sb1", "svc1") require.Error(t, err) assert.True(t, types.IsUnavailable(err)) } diff --git a/sdk/go/openshell/v1/fake/ssh.go b/sdk/go/openshell/v1/fake/ssh.go index 8bfa29c294..950d1f1189 100644 --- a/sdk/go/openshell/v1/fake/ssh.go +++ b/sdk/go/openshell/v1/fake/ssh.go @@ -32,11 +32,11 @@ func (c *fakeSSHClient) CreateSession(_ context.Context, _, _ string) (*types.SS } // RevokeSession returns Unimplemented. -func (c *fakeSSHClient) RevokeSession(_ context.Context, _, _ string) (bool, error) { +func (c *fakeSSHClient) RevokeSession(_ context.Context, _, _ string, _ ...v1.DeleteOptions) (*types.DeletionResult, error) { if c.closedFunc() { - return false, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} + return nil, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} } - return false, &types.StatusError{Code: types.ErrorUnimplemented, Message: "RevokeSession is not supported by the fake client"} + return nil, &types.StatusError{Code: types.ErrorUnimplemented, Message: "RevokeSession is not supported by the fake client"} } // Tunnel returns Unimplemented. Ports outside 1-65535 and empty sandbox names diff --git a/sdk/go/openshell/v1/fake/workspace.go b/sdk/go/openshell/v1/fake/workspace.go index 0cf20019a3..1b5599e977 100644 --- a/sdk/go/openshell/v1/fake/workspace.go +++ b/sdk/go/openshell/v1/fake/workspace.go @@ -113,20 +113,20 @@ func (c *fakeWorkspaceClient) ListAll(ctx context.Context, opts ...v1.ListOption // Delete removes a workspace. Unlike the sandbox fake (which treats delete as // idempotent), workspace delete returns NotFound for non-existent workspaces to // match the gateway's workspace deletion behavior. -func (c *fakeWorkspaceClient) Delete(_ context.Context, name string) error { +func (c *fakeWorkspaceClient) Delete(_ context.Context, name string, opts ...v1.DeleteOptions) (*types.DeletionResult, error) { if c.closedFunc() { - return &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} + return nil, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} } if name == "" { - return &types.StatusError{Code: types.ErrorInvalidArgument, Message: "workspace name must not be empty"} + return nil, &types.StatusError{Code: types.ErrorInvalidArgument, Message: "workspace name must not be empty"} } _, existed := c.workspaceStore.DeleteAndGet("", name) if !existed { - return &types.StatusError{Code: types.ErrorNotFound, Message: name + " not found"} + return deletionResult(false, "", opts) } c.memberStore.DeleteWorkspace(name) - return nil + return deletionResult(true, "", opts) } func (c *fakeWorkspaceClient) AddMember(_ context.Context, workspace, principalSubject string, role types.WorkspaceRole) (*types.WorkspaceMember, error) { @@ -154,22 +154,22 @@ func (c *fakeWorkspaceClient) AddMember(_ context.Context, workspace, principalS return c.memberStore.Create(workspace, member) } -func (c *fakeWorkspaceClient) RemoveMember(_ context.Context, workspace, principalSubject string) error { +func (c *fakeWorkspaceClient) RemoveMember(_ context.Context, workspace, principalSubject string, opts ...v1.DeleteOptions) (*types.DeletionResult, error) { if c.closedFunc() { - return &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} + return nil, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} } if workspace == "" { - return &types.StatusError{Code: types.ErrorInvalidArgument, Message: "workspace name must not be empty"} + return nil, &types.StatusError{Code: types.ErrorInvalidArgument, Message: "workspace name must not be empty"} } if principalSubject == "" { - return &types.StatusError{Code: types.ErrorInvalidArgument, Message: "principal subject must not be empty"} + return nil, &types.StatusError{Code: types.ErrorInvalidArgument, Message: "principal subject must not be empty"} } _, existed := c.memberStore.DeleteAndGet(workspace, principalSubject) if !existed { - return &types.StatusError{Code: types.ErrorNotFound, Message: principalSubject + " not found"} + return deletionResult(false, "", opts) } - return nil + return deletionResult(true, "", opts) } // ListMembers returns all members for the workspace. ListOptions are accepted for interface compatibility but filtering is not implemented. diff --git a/sdk/go/openshell/v1/fake/workspace_test.go b/sdk/go/openshell/v1/fake/workspace_test.go index c9a909ffdc..af7e5552c4 100644 --- a/sdk/go/openshell/v1/fake/workspace_test.go +++ b/sdk/go/openshell/v1/fake/workspace_test.go @@ -19,7 +19,8 @@ func TestWorkspaceDelete_RemovesMembers(t *testing.T) { require.NoError(t, err) _, err = fc.Workspaces().AddMember(ctx, "team", "alice", types.WorkspaceRoleUser) require.NoError(t, err) - require.NoError(t, fc.Workspaces().Delete(ctx, "team")) + _, err = fc.Workspaces().Delete(ctx, "team") + require.NoError(t, err) members, err := fc.Workspaces().ListAllMembers(ctx, "team") require.NoError(t, err) @@ -117,7 +118,7 @@ func TestFakeWorkspace_Delete(t *testing.T) { fc := NewClient() _, _ = fc.Workspaces().Create(context.Background(), "del-ws", nil) - err := fc.Workspaces().Delete(context.Background(), "del-ws") + _, err := fc.Workspaces().Delete(context.Background(), "del-ws") require.NoError(t, err) _, err = fc.Workspaces().Get(context.Background(), "del-ws") @@ -126,7 +127,7 @@ func TestFakeWorkspace_Delete(t *testing.T) { func TestFakeWorkspace_Delete_EmptyName(t *testing.T) { fc := NewClient() - err := fc.Workspaces().Delete(context.Background(), "") + _, err := fc.Workspaces().Delete(context.Background(), "") require.Error(t, err) assert.True(t, types.IsInvalidArgument(err)) @@ -134,7 +135,7 @@ func TestFakeWorkspace_Delete_EmptyName(t *testing.T) { func TestFakeWorkspace_Delete_NotFound(t *testing.T) { fc := NewClient() - err := fc.Workspaces().Delete(context.Background(), "missing") + _, err := fc.Workspaces().Delete(context.Background(), "missing") require.Error(t, err) assert.True(t, types.IsNotFound(err)) @@ -188,7 +189,7 @@ func TestFakeWorkspace_RemoveMember(t *testing.T) { fc := NewClient() _, _ = fc.Workspaces().AddMember(context.Background(), "ws", "user@example.com", types.WorkspaceRoleAdmin) - err := fc.Workspaces().RemoveMember(context.Background(), "ws", "user@example.com") + _, err := fc.Workspaces().RemoveMember(context.Background(), "ws", "user@example.com") require.NoError(t, err) members, err := fc.Workspaces().ListAllMembers(context.Background(), "ws") @@ -198,7 +199,7 @@ func TestFakeWorkspace_RemoveMember(t *testing.T) { func TestFakeWorkspace_RemoveMember_EmptyWorkspace(t *testing.T) { fc := NewClient() - err := fc.Workspaces().RemoveMember(context.Background(), "", "user@example.com") + _, err := fc.Workspaces().RemoveMember(context.Background(), "", "user@example.com") require.Error(t, err) assert.True(t, types.IsInvalidArgument(err)) @@ -206,7 +207,7 @@ func TestFakeWorkspace_RemoveMember_EmptyWorkspace(t *testing.T) { func TestFakeWorkspace_RemoveMember_EmptySubject(t *testing.T) { fc := NewClient() - err := fc.Workspaces().RemoveMember(context.Background(), "ws", "") + _, err := fc.Workspaces().RemoveMember(context.Background(), "ws", "") require.Error(t, err) assert.True(t, types.IsInvalidArgument(err)) @@ -214,7 +215,7 @@ func TestFakeWorkspace_RemoveMember_EmptySubject(t *testing.T) { func TestFakeWorkspace_RemoveMember_NotFound(t *testing.T) { fc := NewClient() - err := fc.Workspaces().RemoveMember(context.Background(), "ws", "missing@example.com") + _, err := fc.Workspaces().RemoveMember(context.Background(), "ws", "missing@example.com") require.Error(t, err) assert.True(t, types.IsNotFound(err)) @@ -267,13 +268,13 @@ func TestFakeWorkspace_Closed(t *testing.T) { _, err = fc.Workspaces().ListAll(context.Background()) assert.True(t, types.IsUnavailable(err)) - err = fc.Workspaces().Delete(context.Background(), "ws") + _, err = fc.Workspaces().Delete(context.Background(), "ws") assert.True(t, types.IsUnavailable(err)) _, err = fc.Workspaces().AddMember(context.Background(), "ws", "user", types.WorkspaceRoleAdmin) assert.True(t, types.IsUnavailable(err)) - err = fc.Workspaces().RemoveMember(context.Background(), "ws", "user") + _, err = fc.Workspaces().RemoveMember(context.Background(), "ws", "user") assert.True(t, types.IsUnavailable(err)) _, err = fc.Workspaces().ListAllMembers(context.Background(), "ws") diff --git a/sdk/go/openshell/v1/file_client_test.go b/sdk/go/openshell/v1/file_client_test.go index ac08c8dec0..f1e794df6d 100644 --- a/sdk/go/openshell/v1/file_client_test.go +++ b/sdk/go/openshell/v1/file_client_test.go @@ -98,7 +98,7 @@ func TestFileUpload(t *testing.T) { GatewayHost: "gateway.example.com", GatewayPort: 2222, } - mock.revokeResp = &pb.RevokeSshSessionResponse{Revoked: true} + mock.revokeResp = &pb.RevokeSshSessionResponse{Outcome: pb.DeletionOutcome_DELETION_OUTCOME_COMPLETED} client, cleanup := setupFileTest(t, mock) defer cleanup() @@ -154,7 +154,7 @@ func TestFileDownload(t *testing.T) { GatewayHost: "gateway.example.com", GatewayPort: 2222, } - mock.revokeResp = &pb.RevokeSshSessionResponse{Revoked: true} + mock.revokeResp = &pb.RevokeSshSessionResponse{Outcome: pb.DeletionOutcome_DELETION_OUTCOME_COMPLETED} client, cleanup := setupFileTest(t, mock) defer cleanup() @@ -250,7 +250,7 @@ func TestFileUpload_ResolvesNameToID(t *testing.T) { GatewayHost: "gw.example.com", GatewayPort: 2222, } - mock.revokeResp = &pb.RevokeSshSessionResponse{Revoked: true} + mock.revokeResp = &pb.RevokeSshSessionResponse{Outcome: pb.DeletionOutcome_DELETION_OUTCOME_COMPLETED} client, cleanup := setupFileTest(t, mock) defer cleanup() diff --git a/sdk/go/openshell/v1/mutations.go b/sdk/go/openshell/v1/mutations.go new file mode 100644 index 0000000000..431010c3e8 --- /dev/null +++ b/sdk/go/openshell/v1/mutations.go @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" + +// DeletionOutcome distinguishes completion from asynchronous acceptance. +type DeletionOutcome = types.DeletionOutcome + +// Known deletion outcomes. Unrecognized values do not establish completion. +const ( + DeletionUnspecified = types.DeletionUnspecified + DeletionCompleted = types.DeletionCompleted + DeletionAccepted = types.DeletionAccepted + DeletionAlreadyAbsent = types.DeletionAlreadyAbsent +) + +// DeletionResult describes the original target, not a same-name replacement. +type DeletionResult = types.DeletionResult + +// DeleteOptions configures the missing-target contract. +type DeleteOptions = types.DeleteOptions + +func allowMissing(opts []DeleteOptions) bool { + return len(opts) > 0 && opts[0].AllowMissing +} diff --git a/sdk/go/openshell/v1/profile.go b/sdk/go/openshell/v1/profile.go index 4460bfcb96..7de1951e4f 100644 --- a/sdk/go/openshell/v1/profile.go +++ b/sdk/go/openshell/v1/profile.go @@ -61,5 +61,5 @@ type ProfileInterface interface { Import(ctx context.Context, workspace string, items []ProfileImportItem) (*ImportResult, error) Update(ctx context.Context, workspace, id string, expectedResourceVersion uint64, item ProfileImportItem) (*UpdateResult, error) Lint(ctx context.Context, workspace string, items []ProfileImportItem) (*LintResult, error) - Delete(ctx context.Context, workspace, id string) (bool, error) + Delete(ctx context.Context, workspace, id string, opts ...DeleteOptions) (*DeletionResult, error) } diff --git a/sdk/go/openshell/v1/profile_client.go b/sdk/go/openshell/v1/profile_client.go index f1eb66707d..40a84b7fcf 100644 --- a/sdk/go/openshell/v1/profile_client.go +++ b/sdk/go/openshell/v1/profile_client.go @@ -146,13 +146,14 @@ func (p *profileClient) Lint(ctx context.Context, workspace string, items []Prof return result, nil } -func (p *profileClient) Delete(ctx context.Context, workspace, id string) (bool, error) { +func (p *profileClient) Delete(ctx context.Context, workspace, id string, opts ...DeleteOptions) (*DeletionResult, error) { resp, err := p.client.DeleteProviderProfile(ctx, &pb.DeleteProviderProfileRequest{ - Id: id, - Workspace: workspace, + AllowMissing: allowMissing(opts), + Id: id, + Workspace: workspace, }) if err != nil { - return false, converter.FromGRPCError(err) + return nil, converter.FromGRPCError(err) } - return resp.GetDeleted(), nil + return &DeletionResult{Outcome: DeletionOutcome(resp.GetOutcome())}, nil } diff --git a/sdk/go/openshell/v1/profile_client_test.go b/sdk/go/openshell/v1/profile_client_test.go index 86f7898313..32708d58c0 100644 --- a/sdk/go/openshell/v1/profile_client_test.go +++ b/sdk/go/openshell/v1/profile_client_test.go @@ -162,7 +162,7 @@ func (s *mockProfileServer) DeleteProviderProfile(_ context.Context, req *pb.Del return nil, status.Errorf(codes.NotFound, "profile %q not found", req.GetId()) } delete(s.profiles, req.GetId()) - return &pb.DeleteProviderProfileResponse{Deleted: true}, nil + return &pb.DeleteProviderProfileResponse{Outcome: pb.DeletionOutcome_DELETION_OUTCOME_COMPLETED}, nil } // --- Test setup --- @@ -537,7 +537,7 @@ func TestProfileDelete(t *testing.T) { deleted, err := client.Delete(context.Background(), "default", "p1") require.NoError(t, err) - assert.True(t, deleted) + assert.Equal(t, DeletionCompleted, deleted.Outcome) // Verify subsequent Get returns NotFound profile, err := client.Get(context.Background(), "default", "p1") @@ -553,7 +553,7 @@ func TestProfileDelete_NotFound(t *testing.T) { deleted, err := client.Delete(context.Background(), "default", "nonexistent") - assert.False(t, deleted) + assert.Nil(t, deleted) require.Error(t, err) assert.True(t, IsNotFound(err)) } @@ -566,6 +566,6 @@ func TestProfileDelete_Error(t *testing.T) { deleted, err := client.Delete(context.Background(), "default", "p1") - assert.False(t, deleted) + assert.Nil(t, deleted) require.Error(t, err) } diff --git a/sdk/go/openshell/v1/provider.go b/sdk/go/openshell/v1/provider.go index 99c0bd731c..b62960dac1 100644 --- a/sdk/go/openshell/v1/provider.go +++ b/sdk/go/openshell/v1/provider.go @@ -23,7 +23,7 @@ type ProviderInterface interface { List(workspace string, opts ...ListOptions) (*Pager[*Provider], error) ListAll(ctx context.Context, workspace string, opts ...ListOptions) ([]*Provider, error) Update(ctx context.Context, workspace string, provider *Provider) (*Provider, error) - Delete(ctx context.Context, workspace, name string) error + Delete(ctx context.Context, workspace, name string, opts ...DeleteOptions) (*DeletionResult, error) Ensure(ctx context.Context, workspace string, provider *Provider) (*Provider, error) Profiles() ProfileInterface Refresh() RefreshInterface diff --git a/sdk/go/openshell/v1/provider_client.go b/sdk/go/openshell/v1/provider_client.go index 15b3543089..08f75ead4c 100644 --- a/sdk/go/openshell/v1/provider_client.go +++ b/sdk/go/openshell/v1/provider_client.go @@ -116,15 +116,16 @@ func (p *providerClient) Update(ctx context.Context, workspace string, provider return converter.ProviderFromProto(resp.GetProvider()), nil } -func (p *providerClient) Delete(ctx context.Context, workspace, name string) error { - _, err := p.client.DeleteProvider(ctx, &pb.DeleteProviderRequest{ +func (p *providerClient) Delete(ctx context.Context, workspace, name string, opts ...DeleteOptions) (*DeletionResult, error) { + resp, err := p.client.DeleteProvider(ctx, &pb.DeleteProviderRequest{ + AllowMissing: allowMissing(opts), Name: name, WorkspaceScope: namedWorkspaceScope(workspace), }) if err != nil { - return converter.FromGRPCError(err) + return nil, converter.FromGRPCError(err) } - return nil + return &DeletionResult{Outcome: DeletionOutcome(resp.GetOutcome())}, nil } func (p *providerClient) Ensure(ctx context.Context, workspace string, provider *Provider) (*Provider, error) { diff --git a/sdk/go/openshell/v1/provider_client_test.go b/sdk/go/openshell/v1/provider_client_test.go index a05d975a45..7ce76f8791 100644 --- a/sdk/go/openshell/v1/provider_client_test.go +++ b/sdk/go/openshell/v1/provider_client_test.go @@ -292,7 +292,7 @@ func TestProviderDelete(t *testing.T) { client, cleanup := setupProviderTest(t, mock) defer cleanup() - err := client.Delete(context.Background(), "default", "deleteme") + _, err := client.Delete(context.Background(), "default", "deleteme") require.NoError(t, err) assert.Empty(t, mock.providers["deleteme"]) @@ -304,7 +304,7 @@ func TestProviderDelete_NotFound(t *testing.T) { client, cleanup := setupProviderTest(t, mock) defer cleanup() - err := client.Delete(context.Background(), "default", "nonexistent") + _, err := client.Delete(context.Background(), "default", "nonexistent") require.Error(t, err) assert.True(t, IsNotFound(err)) diff --git a/sdk/go/openshell/v1/refresh.go b/sdk/go/openshell/v1/refresh.go index 53c39a0ac9..c676697fb4 100644 --- a/sdk/go/openshell/v1/refresh.go +++ b/sdk/go/openshell/v1/refresh.go @@ -32,5 +32,5 @@ type RefreshInterface interface { GetStatus(ctx context.Context, workspace, provider, credentialKey string) ([]*RefreshStatus, error) Configure(ctx context.Context, workspace string, config *RefreshConfig) (*RefreshStatus, error) Rotate(ctx context.Context, workspace, provider, credentialKey string) (*RefreshStatus, error) - Delete(ctx context.Context, workspace, provider, credentialKey string) (bool, error) + Delete(ctx context.Context, workspace, provider, credentialKey string, opts ...DeleteOptions) (*DeletionResult, error) } diff --git a/sdk/go/openshell/v1/refresh_client.go b/sdk/go/openshell/v1/refresh_client.go index dc182da844..5038403871 100644 --- a/sdk/go/openshell/v1/refresh_client.go +++ b/sdk/go/openshell/v1/refresh_client.go @@ -58,14 +58,15 @@ func (r *refreshClient) Rotate(ctx context.Context, workspace, provider, credent return converter.RefreshStatusFromProto(resp.GetStatus()), nil } -func (r *refreshClient) Delete(ctx context.Context, workspace, provider, credentialKey string) (bool, error) { +func (r *refreshClient) Delete(ctx context.Context, workspace, provider, credentialKey string, opts ...DeleteOptions) (*DeletionResult, error) { resp, err := r.client.DeleteProviderRefresh(ctx, &pb.DeleteProviderRefreshRequest{ + AllowMissing: allowMissing(opts), Provider: provider, CredentialKey: credentialKey, WorkspaceScope: namedWorkspaceScope(workspace), }) if err != nil { - return false, converter.FromGRPCError(err) + return nil, converter.FromGRPCError(err) } - return resp.GetDeleted(), nil + return &DeletionResult{Outcome: DeletionOutcome(resp.GetOutcome())}, nil } diff --git a/sdk/go/openshell/v1/refresh_client_test.go b/sdk/go/openshell/v1/refresh_client_test.go index 013de70b85..dee409b81e 100644 --- a/sdk/go/openshell/v1/refresh_client_test.go +++ b/sdk/go/openshell/v1/refresh_client_test.go @@ -114,10 +114,13 @@ func (s *mockRefreshServer) DeleteProviderRefresh(_ context.Context, req *pb.Del key := refreshKey(req.GetProvider(), req.GetCredentialKey()) _, ok := s.statuses[key] if !ok { - return &pb.DeleteProviderRefreshResponse{Deleted: false}, nil + if !req.AllowMissing { + return nil, status.Error(codes.NotFound, "refresh configuration not found") + } + return &pb.DeleteProviderRefreshResponse{Outcome: pb.DeletionOutcome_DELETION_OUTCOME_ALREADY_ABSENT}, nil } delete(s.statuses, key) - return &pb.DeleteProviderRefreshResponse{Deleted: true}, nil + return &pb.DeleteProviderRefreshResponse{Outcome: pb.DeletionOutcome_DELETION_OUTCOME_COMPLETED}, nil } // --- Test setup --- @@ -354,7 +357,7 @@ func TestRefreshDelete(t *testing.T) { deleted, err := client.Delete(context.Background(), "default", "openai", "api-key") require.NoError(t, err) - assert.True(t, deleted) + assert.Equal(t, DeletionCompleted, deleted.Outcome) // Verify it's gone statuses, err := client.GetStatus(context.Background(), "default", "openai", "api-key") @@ -367,10 +370,10 @@ func TestRefreshDelete_NotConfigured(t *testing.T) { client, cleanup := setupRefreshTest(t, mock) defer cleanup() - deleted, err := client.Delete(context.Background(), "default", "openai", "nonexistent") + deleted, err := client.Delete(context.Background(), "default", "openai", "nonexistent", DeleteOptions{AllowMissing: true}) require.NoError(t, err) - assert.False(t, deleted) + assert.Equal(t, DeletionAlreadyAbsent, deleted.Outcome) } func TestRefreshDelete_Error(t *testing.T) { @@ -381,7 +384,7 @@ func TestRefreshDelete_Error(t *testing.T) { deleted, err := client.Delete(context.Background(), "default", "openai", "key") - assert.False(t, deleted) + assert.Nil(t, deleted) require.Error(t, err) } @@ -418,7 +421,7 @@ func TestRefreshLifecycle(t *testing.T) { // 4. Delete deleted, err := client.Delete(ctx, "default", "openai", "api-key") require.NoError(t, err) - assert.True(t, deleted) + assert.Equal(t, DeletionCompleted, deleted.Outcome) // 5. Verify removed statuses, err = client.GetStatus(ctx, "default", "openai", "api-key") diff --git a/sdk/go/openshell/v1/sandbox.go b/sdk/go/openshell/v1/sandbox.go index 062894c08c..89ba2859d7 100644 --- a/sdk/go/openshell/v1/sandbox.go +++ b/sdk/go/openshell/v1/sandbox.go @@ -77,7 +77,7 @@ type SandboxInterface interface { ListAll(ctx context.Context, workspace string, opts ...ListOptions) ([]*Sandbox, error) Stop(ctx context.Context, workspace, name string) (*Sandbox, error) Start(ctx context.Context, workspace, name string) (*Sandbox, error) - Delete(ctx context.Context, workspace, name string) error + Delete(ctx context.Context, workspace, name string, opts ...DeleteOptions) (*DeletionResult, error) AttachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*AttachProviderResult, error) DetachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*DetachProviderResult, error) ListProviders(ctx context.Context, workspace, sandboxName string) ([]*Provider, error) diff --git a/sdk/go/openshell/v1/sandbox_client.go b/sdk/go/openshell/v1/sandbox_client.go index 2ea59dc4a2..bf6e482472 100644 --- a/sdk/go/openshell/v1/sandbox_client.go +++ b/sdk/go/openshell/v1/sandbox_client.go @@ -141,15 +141,16 @@ func (s *sandboxClient) ListAll(ctx context.Context, workspace string, opts ...L return pager.All(ctx) } -func (s *sandboxClient) Delete(ctx context.Context, workspace, name string) error { - _, err := s.client.DeleteSandbox(ctx, &pb.DeleteSandboxRequest{ +func (s *sandboxClient) Delete(ctx context.Context, workspace, name string, opts ...DeleteOptions) (*DeletionResult, error) { + resp, err := s.client.DeleteSandbox(ctx, &pb.DeleteSandboxRequest{ + AllowMissing: allowMissing(opts), Name: name, WorkspaceScope: namedWorkspaceScope(workspace), }) if err != nil { - return converter.FromGRPCError(err) + return nil, converter.FromGRPCError(err) } - return nil + return &DeletionResult{Outcome: DeletionOutcome(resp.GetOutcome()), SandboxID: resp.GetSandboxId()}, nil } func (s *sandboxClient) Stop(ctx context.Context, workspace, name string) (*Sandbox, error) { diff --git a/sdk/go/openshell/v1/sandbox_client_test.go b/sdk/go/openshell/v1/sandbox_client_test.go index 18674ea76c..285cdc0ef0 100644 --- a/sdk/go/openshell/v1/sandbox_client_test.go +++ b/sdk/go/openshell/v1/sandbox_client_test.go @@ -34,6 +34,8 @@ type mockSandboxServer struct { listPages [][]*pb.Sandbox listRequests []*pb.ListSandboxesRequest deleteErr error + deleteResponse *pb.DeleteSandboxResponse + deleteRequest *pb.DeleteSandboxRequest attachErr error detachErr error listProvErr error @@ -132,6 +134,10 @@ func (s *mockSandboxServer) ListSandboxes(_ context.Context, req *pb.ListSandbox func (s *mockSandboxServer) DeleteSandbox(_ context.Context, req *pb.DeleteSandboxRequest) (*pb.DeleteSandboxResponse, error) { s.mu.Lock() defer s.mu.Unlock() + s.deleteRequest = req + if s.deleteResponse != nil { + return s.deleteResponse, nil + } if s.deleteErr != nil { return nil, s.deleteErr } @@ -140,7 +146,7 @@ func (s *mockSandboxServer) DeleteSandbox(_ context.Context, req *pb.DeleteSandb return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName()) } delete(s.sandboxes, req.GetName()) - return &pb.DeleteSandboxResponse{Deleted: true}, nil + return &pb.DeleteSandboxResponse{Outcome: pb.DeletionOutcome_DELETION_OUTCOME_COMPLETED}, nil } func (s *mockSandboxServer) StopSandbox(_ context.Context, req *pb.StopSandboxRequest) (*pb.SandboxResponse, error) { @@ -503,18 +509,32 @@ func TestSandboxDelete(t *testing.T) { client, cleanup := setupSandboxTest(t, mock) defer cleanup() - err := client.Delete(context.Background(), "default", "deleteme") + _, err := client.Delete(context.Background(), "default", "deleteme") require.NoError(t, err) assert.Empty(t, mock.sandboxes["deleteme"]) } +func TestSandboxDelete_OutcomesAndOptions(t *testing.T) { + for _, outcome := range []int32{0, 1, 2, 3, 99} { + mock := newMockSandboxServer() + mock.deleteResponse = &pb.DeleteSandboxResponse{Outcome: pb.DeletionOutcome(outcome), SandboxId: "original-id"} + client, cleanup := setupSandboxTest(t, mock) + t.Cleanup(cleanup) + result, err := client.Delete(context.Background(), "default", "sandbox", DeleteOptions{AllowMissing: true}) + require.NoError(t, err) + assert.Equal(t, DeletionOutcome(outcome), result.Outcome) + assert.Equal(t, "original-id", result.SandboxID) + assert.True(t, mock.deleteRequest.GetAllowMissing()) + } +} + func TestSandboxDelete_NotFound(t *testing.T) { mock := newMockSandboxServer() client, cleanup := setupSandboxTest(t, mock) defer cleanup() - err := client.Delete(context.Background(), "default", "nonexistent") + _, err := client.Delete(context.Background(), "default", "nonexistent") require.Error(t, err) assert.True(t, IsNotFound(err)) diff --git a/sdk/go/openshell/v1/sandbox_template.go b/sdk/go/openshell/v1/sandbox_template.go index ed635177d1..52cb4f2784 100644 --- a/sdk/go/openshell/v1/sandbox_template.go +++ b/sdk/go/openshell/v1/sandbox_template.go @@ -42,5 +42,5 @@ type SandboxTemplateInterface interface { Get(ctx context.Context, workspace, name string) (*SandboxWorkloadTemplate, error) List(workspace string, opts ...ListOptions) (*Pager[*SandboxWorkloadTemplate], error) ListAll(ctx context.Context, workspace string, opts ...ListOptions) ([]*SandboxWorkloadTemplate, error) - Delete(ctx context.Context, workspace, name string) (bool, error) + Delete(ctx context.Context, workspace, name string, opts ...DeleteOptions) (*DeletionResult, error) } diff --git a/sdk/go/openshell/v1/sandbox_template_client.go b/sdk/go/openshell/v1/sandbox_template_client.go index 511b6f9952..bcf48185e9 100644 --- a/sdk/go/openshell/v1/sandbox_template_client.go +++ b/sdk/go/openshell/v1/sandbox_template_client.go @@ -91,13 +91,14 @@ func (s *sandboxTemplateClient) ListAll(ctx context.Context, workspace string, o return pager.All(ctx) } -func (s *sandboxTemplateClient) Delete(ctx context.Context, workspace, name string) (bool, error) { +func (s *sandboxTemplateClient) Delete(ctx context.Context, workspace, name string, opts ...DeleteOptions) (*DeletionResult, error) { resp, err := s.client.DeleteSandboxTemplate(ctx, &pb.DeleteSandboxTemplateRequest{ + AllowMissing: allowMissing(opts), Name: name, WorkspaceScope: namedWorkspaceScope(workspace), }) if err != nil { - return false, converter.FromGRPCError(err) + return nil, converter.FromGRPCError(err) } - return resp.GetDeleted(), nil + return &DeletionResult{Outcome: DeletionOutcome(resp.GetOutcome())}, nil } diff --git a/sdk/go/openshell/v1/sandbox_template_client_test.go b/sdk/go/openshell/v1/sandbox_template_client_test.go index 115d31f097..8d745344e2 100644 --- a/sdk/go/openshell/v1/sandbox_template_client_test.go +++ b/sdk/go/openshell/v1/sandbox_template_client_test.go @@ -99,7 +99,7 @@ func (s *mockSandboxTemplateServer) DeleteSandboxTemplate(_ context.Context, req return nil, s.deleteErr } delete(s.templates, req.GetName()) - return &pb.DeleteSandboxTemplateResponse{Deleted: true}, nil + return &pb.DeleteSandboxTemplateResponse{Outcome: pb.DeletionOutcome_DELETION_OUTCOME_COMPLETED}, nil } func setupSandboxTemplateTest(t *testing.T, mock *mockSandboxTemplateServer) (*sandboxTemplateClient, func()) { @@ -230,7 +230,7 @@ func TestSandboxTemplateGetListDelete(t *testing.T) { deleted, err := client.Delete(context.Background(), "default", "gpu-kata") require.NoError(t, err) - assert.True(t, deleted) + assert.Equal(t, DeletionCompleted, deleted.Outcome) mock.mu.Lock() defer mock.mu.Unlock() diff --git a/sdk/go/openshell/v1/service.go b/sdk/go/openshell/v1/service.go index 44a316be3e..3d53069afa 100644 --- a/sdk/go/openshell/v1/service.go +++ b/sdk/go/openshell/v1/service.go @@ -18,5 +18,5 @@ type ServiceInterface interface { Get(ctx context.Context, workspace, sandboxName, serviceName string) (*ServiceEndpoint, error) List(workspace, sandboxName string, opts ...ListOptions) (*Pager[*ServiceEndpoint], error) ListAll(ctx context.Context, workspace, sandboxName string, opts ...ListOptions) ([]*ServiceEndpoint, error) - Delete(ctx context.Context, workspace, sandboxName, serviceName string) error + Delete(ctx context.Context, workspace, sandboxName, serviceName string, opts ...DeleteOptions) (*DeletionResult, error) } diff --git a/sdk/go/openshell/v1/service_client.go b/sdk/go/openshell/v1/service_client.go index fcca16936b..78de844b98 100644 --- a/sdk/go/openshell/v1/service_client.go +++ b/sdk/go/openshell/v1/service_client.go @@ -85,14 +85,15 @@ func (s *serviceClient) ListAll(ctx context.Context, workspace, sandboxName stri return pager.All(ctx) } -func (s *serviceClient) Delete(ctx context.Context, workspace, sandboxName, serviceName string) error { - _, err := s.client.DeleteService(ctx, &pb.DeleteServiceRequest{ +func (s *serviceClient) Delete(ctx context.Context, workspace, sandboxName, serviceName string, opts ...DeleteOptions) (*DeletionResult, error) { + resp, err := s.client.DeleteService(ctx, &pb.DeleteServiceRequest{ + AllowMissing: allowMissing(opts), Sandbox: sandboxName, Service: serviceName, WorkspaceScope: namedWorkspaceScope(workspace), }) if err != nil { - return converter.FromGRPCError(err) + return nil, converter.FromGRPCError(err) } - return nil + return &DeletionResult{Outcome: DeletionOutcome(resp.GetOutcome())}, nil } diff --git a/sdk/go/openshell/v1/service_client_test.go b/sdk/go/openshell/v1/service_client_test.go index ade5de5af4..9c80176867 100644 --- a/sdk/go/openshell/v1/service_client_test.go +++ b/sdk/go/openshell/v1/service_client_test.go @@ -114,7 +114,7 @@ func (s *mockServiceServer) DeleteService(_ context.Context, req *pb.DeleteServi return nil, status.Errorf(codes.NotFound, "service %q not found in sandbox %q", req.GetService(), req.GetSandbox()) } delete(s.endpoints, key) - return &pb.DeleteServiceResponse{Deleted: true}, nil + return &pb.DeleteServiceResponse{Outcome: pb.DeletionOutcome_DELETION_OUTCOME_COMPLETED}, nil } // --- Test setup --- @@ -308,7 +308,7 @@ func TestServiceDelete(t *testing.T) { _, err := client.Expose(context.Background(), "default", "web-app", "api", 8080, true) require.NoError(t, err) - err = client.Delete(context.Background(), "default", "web-app", "api") + _, err = client.Delete(context.Background(), "default", "web-app", "api") require.NoError(t, err) @@ -324,7 +324,7 @@ func TestServiceDelete_NotFound(t *testing.T) { client, cleanup := setupServiceTest(t, mock) defer cleanup() - err := client.Delete(context.Background(), "default", "web-app", "nonexistent") + _, err := client.Delete(context.Background(), "default", "web-app", "nonexistent") require.Error(t, err) assert.True(t, IsNotFound(err)) @@ -336,7 +336,7 @@ func TestServiceDelete_Error(t *testing.T) { client, cleanup := setupServiceTest(t, mock) defer cleanup() - err := client.Delete(context.Background(), "default", "web-app", "api") + _, err := client.Delete(context.Background(), "default", "web-app", "api") require.Error(t, err) } diff --git a/sdk/go/openshell/v1/ssh.go b/sdk/go/openshell/v1/ssh.go index 19a4b65a3e..560bef4125 100644 --- a/sdk/go/openshell/v1/ssh.go +++ b/sdk/go/openshell/v1/ssh.go @@ -32,6 +32,6 @@ func WithTunnelServiceID(id string) TunnelOption { // SSHInterface defines operations for managing SSH sessions. type SSHInterface interface { CreateSession(ctx context.Context, workspace, sandboxID string) (*SSHSession, error) - RevokeSession(ctx context.Context, workspace, token string) (bool, error) + RevokeSession(ctx context.Context, workspace, token string, opts ...DeleteOptions) (*DeletionResult, error) Tunnel(ctx context.Context, workspace, sandboxName string, port uint32, opts ...TunnelOption) (io.ReadWriteCloser, error) } diff --git a/sdk/go/openshell/v1/ssh_client.go b/sdk/go/openshell/v1/ssh_client.go index 6e7ee1a462..61bb3f14ff 100644 --- a/sdk/go/openshell/v1/ssh_client.go +++ b/sdk/go/openshell/v1/ssh_client.go @@ -40,14 +40,15 @@ func (s *sshClient) CreateSession(ctx context.Context, _, sandboxID string) (*SS return converter.SSHSessionFromProto(resp), nil } -func (s *sshClient) RevokeSession(ctx context.Context, _, token string) (bool, error) { +func (s *sshClient) RevokeSession(ctx context.Context, _, token string, opts ...DeleteOptions) (*DeletionResult, error) { resp, err := s.client.RevokeSshSession(ctx, &pb.RevokeSshSessionRequest{ - Token: token, + AllowMissing: allowMissing(opts), + Token: token, }) if err != nil { - return false, converter.FromGRPCError(err) + return nil, converter.FromGRPCError(err) } - return resp.GetRevoked(), nil + return &DeletionResult{Outcome: DeletionOutcome(resp.GetOutcome())}, nil } func (s *sshClient) Tunnel(ctx context.Context, workspace, sandboxName string, port uint32, opts ...TunnelOption) (io.ReadWriteCloser, error) { @@ -141,7 +142,7 @@ func (s *sshClient) Tunnel(ctx context.Context, workspace, sandboxName string, p func (s *sshClient) revokeSessionForCleanup(workspace, token string) { ctx, cancel := context.WithTimeout(context.Background(), sshCleanupTimeout) defer cancel() - _, _ = s.RevokeSession(ctx, workspace, token) + _, _ = s.RevokeSession(ctx, workspace, token, DeleteOptions{AllowMissing: true}) } type sshTunnel struct { diff --git a/sdk/go/openshell/v1/ssh_client_test.go b/sdk/go/openshell/v1/ssh_client_test.go index 72ead9c829..6c2da9f539 100644 --- a/sdk/go/openshell/v1/ssh_client_test.go +++ b/sdk/go/openshell/v1/ssh_client_test.go @@ -81,13 +81,15 @@ func (s *mockSSHServer) RevokeSshSession(ctx context.Context, req *pb.RevokeSshS } token := req.GetToken() - active, exists := s.tokens[token] - if exists && active { + _, exists := s.tokens[token] + if exists { s.tokens[token] = false - return &pb.RevokeSshSessionResponse{Revoked: true}, nil + return &pb.RevokeSshSessionResponse{Outcome: pb.DeletionOutcome_DELETION_OUTCOME_COMPLETED}, nil } - // Already revoked or not found — not an error, just revoked=false. - return &pb.RevokeSshSessionResponse{Revoked: false}, nil + if !req.AllowMissing { + return nil, status.Error(codes.NotFound, "ssh session not found") + } + return &pb.RevokeSshSessionResponse{Outcome: pb.DeletionOutcome_DELETION_OUTCOME_ALREADY_ABSENT}, nil } func (s *mockSSHServer) ForwardTcp(stream grpc.BidiStreamingServer[pb.TcpForwardFrame, pb.TcpForwardFrame]) error { //nolint:revive // proto-generated method name @@ -158,7 +160,9 @@ func (m *mockSandboxResolver) List(_ string, _ ...ListOptions) (*Pager[*Sandbox] func (m *mockSandboxResolver) ListAll(_ context.Context, _ string, _ ...ListOptions) ([]*Sandbox, error) { return nil, nil } -func (m *mockSandboxResolver) Delete(_ context.Context, _, _ string) error { return nil } +func (m *mockSandboxResolver) Delete(_ context.Context, _, _ string, _ ...DeleteOptions) (*DeletionResult, error) { + return &DeletionResult{Outcome: DeletionCompleted}, nil +} func (m *mockSandboxResolver) AttachProvider(_ context.Context, _, _, _ string, _ uint64) (*AttachProviderResult, error) { return nil, nil } @@ -261,7 +265,7 @@ func TestSSHRevokeSession(t *testing.T) { revoked, err := client.RevokeSession(context.Background(), "default", session.Token) require.NoError(t, err) - assert.True(t, revoked) + assert.Equal(t, DeletionCompleted, revoked.Outcome) } func TestSSHRevokeSession_AlreadyRevoked(t *testing.T) { @@ -275,11 +279,11 @@ func TestSSHRevokeSession_AlreadyRevoked(t *testing.T) { _, err = client.RevokeSession(context.Background(), "default", session.Token) require.NoError(t, err) - // Revoke again — should return false (already revoked). + // Revocation is already effective; the retained session is still completed. revoked, err := client.RevokeSession(context.Background(), "default", session.Token) require.NoError(t, err) - assert.False(t, revoked) + assert.Equal(t, DeletionCompleted, revoked.Outcome) } func TestSSHRevokeSession_Error(t *testing.T) { @@ -290,7 +294,7 @@ func TestSSHRevokeSession_Error(t *testing.T) { revoked, err := client.RevokeSession(context.Background(), "default", "some-token") - assert.False(t, revoked) + assert.Nil(t, revoked) require.Error(t, err) var se *StatusError require.ErrorAs(t, err, &se) diff --git a/sdk/go/openshell/v1/tcp_client_test.go b/sdk/go/openshell/v1/tcp_client_test.go index 4ea96dbbab..40c4771a72 100644 --- a/sdk/go/openshell/v1/tcp_client_test.go +++ b/sdk/go/openshell/v1/tcp_client_test.go @@ -784,8 +784,8 @@ func (m *mockSSHClient) CreateSession(_ context.Context, _, _ string) (*SSHSessi return nil, fmt.Errorf("not implemented in mock") } -func (m *mockSSHClient) RevokeSession(_ context.Context, _, _ string) (bool, error) { - return false, fmt.Errorf("not implemented in mock") +func (m *mockSSHClient) RevokeSession(_ context.Context, _, _ string, _ ...DeleteOptions) (*DeletionResult, error) { + return nil, fmt.Errorf("not implemented in mock") } // Tunnel returns a pipe that echoes data back, and increments the call counter. @@ -1002,7 +1002,7 @@ func (r *flippableResolver) List(string, ...ListOptions) (*Pager[*Sandbox], erro func (r *flippableResolver) ListAll(context.Context, string, ...ListOptions) ([]*Sandbox, error) { panic("not implemented") } -func (r *flippableResolver) Delete(context.Context, string, string) error { +func (r *flippableResolver) Delete(context.Context, string, string, ...DeleteOptions) (*DeletionResult, error) { panic("not implemented") } func (r *flippableResolver) AttachProvider(context.Context, string, string, string, uint64) (*AttachProviderResult, error) { diff --git a/sdk/go/openshell/v1/types/mutations.go b/sdk/go/openshell/v1/types/mutations.go new file mode 100644 index 0000000000..3ed6b16d84 --- /dev/null +++ b/sdk/go/openshell/v1/types/mutations.go @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +// DeletionOutcome distinguishes completion from asynchronous acceptance. +// Unknown numeric values are preserved and must not be treated as completion. +type DeletionOutcome int32 + +// Known deletion outcomes. Only Completed and AlreadyAbsent establish completion. +const ( + DeletionUnspecified DeletionOutcome = iota + DeletionCompleted + DeletionAccepted + DeletionAlreadyAbsent +) + +// DeletionResult describes the original target, not a same-name replacement. +type DeletionResult struct { + Outcome DeletionOutcome + // SandboxID is empty for non-sandbox deletions and missing targets. + SandboxID string +} + +// DeleteOptions configures the missing-target contract. +type DeleteOptions struct { + AllowMissing bool +} diff --git a/sdk/go/openshell/v1/workspace.go b/sdk/go/openshell/v1/workspace.go index cff33185f8..4ce48a3352 100644 --- a/sdk/go/openshell/v1/workspace.go +++ b/sdk/go/openshell/v1/workspace.go @@ -41,9 +41,9 @@ type WorkspaceInterface interface { Get(ctx context.Context, name string) (*Workspace, error) List(opts ...ListOptions) (*Pager[*Workspace], error) ListAll(ctx context.Context, opts ...ListOptions) ([]*Workspace, error) - Delete(ctx context.Context, name string) error + Delete(ctx context.Context, name string, opts ...DeleteOptions) (*DeletionResult, error) AddMember(ctx context.Context, workspace, principalSubject string, role WorkspaceRole) (*WorkspaceMember, error) - RemoveMember(ctx context.Context, workspace, principalSubject string) error + RemoveMember(ctx context.Context, workspace, principalSubject string, opts ...DeleteOptions) (*DeletionResult, error) ListMembers(workspace string, opts ...ListOptions) (*Pager[*WorkspaceMember], error) ListAllMembers(ctx context.Context, workspace string, opts ...ListOptions) ([]*WorkspaceMember, error) } diff --git a/sdk/go/openshell/v1/workspace_client.go b/sdk/go/openshell/v1/workspace_client.go index e17f268643..d39f81d494 100644 --- a/sdk/go/openshell/v1/workspace_client.go +++ b/sdk/go/openshell/v1/workspace_client.go @@ -80,18 +80,19 @@ func (w *workspaceClient) ListAll(ctx context.Context, opts ...ListOptions) ([]* return pager.All(ctx) } -func (w *workspaceClient) Delete(ctx context.Context, name string) error { +func (w *workspaceClient) Delete(ctx context.Context, name string, opts ...DeleteOptions) (*DeletionResult, error) { if name == "" { - return &StatusError{Code: ErrorInvalidArgument, Message: "workspace name must not be empty"} + return nil, &StatusError{Code: ErrorInvalidArgument, Message: "workspace name must not be empty"} } - _, err := w.client.DeleteWorkspace(ctx, &pb.DeleteWorkspaceRequest{ - Name: name, + resp, err := w.client.DeleteWorkspace(ctx, &pb.DeleteWorkspaceRequest{ + AllowMissing: allowMissing(opts), + Name: name, }) if err != nil { - return converter.FromGRPCError(err) + return nil, converter.FromGRPCError(err) } - return nil + return &DeletionResult{Outcome: DeletionOutcome(resp.GetOutcome())}, nil } func (w *workspaceClient) AddMember(ctx context.Context, workspace, principalSubject string, role WorkspaceRole) (*WorkspaceMember, error) { @@ -118,22 +119,23 @@ func (w *workspaceClient) AddMember(ctx context.Context, workspace, principalSub return converter.WorkspaceMemberFromProto(resp.GetMember()), nil } -func (w *workspaceClient) RemoveMember(ctx context.Context, workspace, principalSubject string) error { +func (w *workspaceClient) RemoveMember(ctx context.Context, workspace, principalSubject string, opts ...DeleteOptions) (*DeletionResult, error) { if workspace == "" { - return &StatusError{Code: ErrorInvalidArgument, Message: "workspace name must not be empty"} + return nil, &StatusError{Code: ErrorInvalidArgument, Message: "workspace name must not be empty"} } if principalSubject == "" { - return &StatusError{Code: ErrorInvalidArgument, Message: "principal subject must not be empty"} + return nil, &StatusError{Code: ErrorInvalidArgument, Message: "principal subject must not be empty"} } - _, err := w.client.RemoveWorkspaceMember(ctx, &pb.RemoveWorkspaceMemberRequest{ + resp, err := w.client.RemoveWorkspaceMember(ctx, &pb.RemoveWorkspaceMemberRequest{ + AllowMissing: allowMissing(opts), Workspace: workspace, PrincipalSubject: principalSubject, }) if err != nil { - return converter.FromGRPCError(err) + return nil, converter.FromGRPCError(err) } - return nil + return &DeletionResult{Outcome: DeletionOutcome(resp.GetOutcome())}, nil } func (w *workspaceClient) ListMembers(workspace string, opts ...ListOptions) (*Pager[*WorkspaceMember], error) { diff --git a/sdk/go/openshell/v1/workspace_test.go b/sdk/go/openshell/v1/workspace_test.go index 9f79c79ae7..79bc53ae6a 100644 --- a/sdk/go/openshell/v1/workspace_test.go +++ b/sdk/go/openshell/v1/workspace_test.go @@ -265,13 +265,13 @@ func TestWorkspaceList_EmptyReturnsNonNilSlice(t *testing.T) { func TestWorkspaceDelete_Success(t *testing.T) { mock := &mockWorkspaceServer{ - deleteResp: &pb.DeleteWorkspaceResponse{Deleted: true}, + deleteResp: &pb.DeleteWorkspaceResponse{Outcome: pb.DeletionOutcome_DELETION_OUTCOME_COMPLETED}, } conn, cleanup := newMockWorkspaceServer(mock) defer cleanup() wc := newWorkspaceClient(conn) - err := wc.Delete(context.Background(), "test-ws") + _, err := wc.Delete(context.Background(), "test-ws") require.NoError(t, err) } @@ -282,7 +282,7 @@ func TestWorkspaceDelete_EmptyName(t *testing.T) { defer cleanup() wc := newWorkspaceClient(conn) - err := wc.Delete(context.Background(), "") + _, err := wc.Delete(context.Background(), "") require.Error(t, err) assert.True(t, IsInvalidArgument(err)) @@ -296,7 +296,7 @@ func TestWorkspaceDelete_NotFound(t *testing.T) { defer cleanup() wc := newWorkspaceClient(conn) - err := wc.Delete(context.Background(), "missing-ws") + _, err := wc.Delete(context.Background(), "missing-ws") require.Error(t, err) assert.True(t, IsNotFound(err)) @@ -387,13 +387,13 @@ func TestAddMember_AlreadyExists(t *testing.T) { func TestRemoveMember_Success(t *testing.T) { mock := &mockWorkspaceServer{ - removeMemberResp: &pb.RemoveWorkspaceMemberResponse{Removed: true}, + removeMemberResp: &pb.RemoveWorkspaceMemberResponse{Outcome: pb.DeletionOutcome_DELETION_OUTCOME_COMPLETED}, } conn, cleanup := newMockWorkspaceServer(mock) defer cleanup() wc := newWorkspaceClient(conn) - err := wc.RemoveMember(context.Background(), "test-ws", "user@example.com") + _, err := wc.RemoveMember(context.Background(), "test-ws", "user@example.com") require.NoError(t, err) } @@ -404,7 +404,7 @@ func TestRemoveMember_EmptyWorkspace(t *testing.T) { defer cleanup() wc := newWorkspaceClient(conn) - err := wc.RemoveMember(context.Background(), "", "user@example.com") + _, err := wc.RemoveMember(context.Background(), "", "user@example.com") require.Error(t, err) assert.True(t, IsInvalidArgument(err)) @@ -416,7 +416,7 @@ func TestRemoveMember_EmptySubject(t *testing.T) { defer cleanup() wc := newWorkspaceClient(conn) - err := wc.RemoveMember(context.Background(), "test-ws", "") + _, err := wc.RemoveMember(context.Background(), "test-ws", "") require.Error(t, err) assert.True(t, IsInvalidArgument(err)) @@ -430,7 +430,7 @@ func TestRemoveMember_NotFound(t *testing.T) { defer cleanup() wc := newWorkspaceClient(conn) - err := wc.RemoveMember(context.Background(), "test-ws", "missing@example.com") + _, err := wc.RemoveMember(context.Background(), "test-ws", "missing@example.com") require.Error(t, err) assert.True(t, IsNotFound(err)) diff --git a/sdk/go/proto/openshellv1/openshell.pb.go b/sdk/go/proto/openshellv1/openshell.pb.go index 63bccf5106..935ceaac47 100644 --- a/sdk/go/proto/openshellv1/openshell.pb.go +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -506,6 +506,69 @@ func (ProviderCredentialRefreshRecoveryAction) EnumDescriptor() ([]byte, []int) return file_openshell_proto_rawDescGZIP(), []int{7} } +// Result of a public delete, membership removal, or session revocation. +// Default requests return NOT_FOUND for a missing target. With allow_missing, +// only a missing target becomes ALREADY_ABSENT; parent lookup, authorization, +// validation, precondition, and backend errors retain their normal status. +// These results describe the targeted resource, not a same-name replacement. +type DeletionOutcome int32 + +const ( + // No outcome was supplied. Never infer completion from this value. + DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED DeletionOutcome = 0 + // The targeted gateway resource is removed (or the SSH session is revoked). + // Downstream platform garbage collection may still be finishing. + DeletionOutcome_DELETION_OUTCOME_COMPLETED DeletionOutcome = 1 + // Sandbox deletion is accepted but its gateway record still exists. + // Observe the targeted sandbox ID until it disappears for completion. + DeletionOutcome_DELETION_OUTCOME_ACCEPTED DeletionOutcome = 2 + // The target did not exist and allow_missing was true. + DeletionOutcome_DELETION_OUTCOME_ALREADY_ABSENT DeletionOutcome = 3 +) + +// Enum value maps for DeletionOutcome. +var ( + DeletionOutcome_name = map[int32]string{ + 0: "DELETION_OUTCOME_UNSPECIFIED", + 1: "DELETION_OUTCOME_COMPLETED", + 2: "DELETION_OUTCOME_ACCEPTED", + 3: "DELETION_OUTCOME_ALREADY_ABSENT", + } + DeletionOutcome_value = map[string]int32{ + "DELETION_OUTCOME_UNSPECIFIED": 0, + "DELETION_OUTCOME_COMPLETED": 1, + "DELETION_OUTCOME_ACCEPTED": 2, + "DELETION_OUTCOME_ALREADY_ABSENT": 3, + } +) + +func (x DeletionOutcome) Enum() *DeletionOutcome { + p := new(DeletionOutcome) + *p = x + return p +} + +func (x DeletionOutcome) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (DeletionOutcome) Descriptor() protoreflect.EnumDescriptor { + return file_openshell_proto_enumTypes[8].Descriptor() +} + +func (DeletionOutcome) Type() protoreflect.EnumType { + return &file_openshell_proto_enumTypes[8] +} + +func (x DeletionOutcome) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use DeletionOutcome.Descriptor instead. +func (DeletionOutcome) EnumDescriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{8} +} + // Last observed network result for a configured external tool endpoint. // Results describe accepted traffic observations, not present availability. type EndpointResult int32 @@ -564,11 +627,11 @@ func (x EndpointResult) String() string { } func (EndpointResult) Descriptor() protoreflect.EnumDescriptor { - return file_openshell_proto_enumTypes[8].Descriptor() + return file_openshell_proto_enumTypes[9].Descriptor() } func (EndpointResult) Type() protoreflect.EnumType { - return &file_openshell_proto_enumTypes[8] + return &file_openshell_proto_enumTypes[9] } func (x EndpointResult) Number() protoreflect.EnumNumber { @@ -577,7 +640,7 @@ func (x EndpointResult) Number() protoreflect.EnumNumber { // Deprecated: Use EndpointResult.Descriptor instead. func (EndpointResult) EnumDescriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{8} + return file_openshell_proto_rawDescGZIP(), []int{9} } // IssueSandboxToken request. Empty body; identity is established by the @@ -2873,8 +2936,11 @@ type DeleteSandboxTemplateRequest struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Explicit workspace scope. The all-workspaces selection is invalid. WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Succeed with ALREADY_ABSENT if the target is missing. Authorization and + // parent-workspace checks still apply. + AllowMissing bool `protobuf:"varint,4,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DeleteSandboxTemplateRequest) Reset() { @@ -2921,6 +2987,13 @@ func (x *DeleteSandboxTemplateRequest) GetWorkspaceScope() *datamodelv1.Workspac return nil } +func (x *DeleteSandboxTemplateRequest) GetAllowMissing() bool { + if x != nil { + return x.AllowMissing + } + return false +} + type SandboxTemplateResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Template *SandboxWorkloadTemplate `protobuf:"bytes,1,opt,name=template,proto3" json:"template,omitempty"` @@ -3020,7 +3093,7 @@ func (x *ListSandboxTemplatesResponse) GetNextPageToken() string { type DeleteSandboxTemplateResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3055,11 +3128,11 @@ func (*DeleteSandboxTemplateResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{38} } -func (x *DeleteSandboxTemplateResponse) GetDeleted() bool { +func (x *DeleteSandboxTemplateResponse) GetOutcome() DeletionOutcome { if x != nil { - return x.Deleted + return x.Outcome } - return false + return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED } // Request a gateway-owned staging slot for a local rootfs tar archive. @@ -3548,8 +3621,11 @@ type DeleteSandboxRequest struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Explicit workspace scope. The all-workspaces selection is invalid. WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Succeed with ALREADY_ABSENT if the target is missing. Does not wait for + // asynchronous cleanup and does not suppress authorization or parent errors. + AllowMissing bool `protobuf:"varint,4,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DeleteSandboxRequest) Reset() { @@ -3596,6 +3672,13 @@ func (x *DeleteSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelecto return nil } +func (x *DeleteSandboxRequest) GetAllowMissing() bool { + if x != nil { + return x.AllowMissing + } + return false +} + // Stop sandbox request. type StopSandboxRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -3960,8 +4043,11 @@ func (x *DetachSandboxProviderResponse) GetDetached() bool { // Delete sandbox response. type DeleteSandboxResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` + // Immutable identity of the targeted sandbox, empty for ALREADY_ABSENT. + // A same-name replacement is not part of this deletion. + SandboxId string `protobuf:"bytes,3,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3996,11 +4082,18 @@ func (*DeleteSandboxResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{54} } -func (x *DeleteSandboxResponse) GetDeleted() bool { +func (x *DeleteSandboxResponse) GetOutcome() DeletionOutcome { if x != nil { - return x.Deleted + return x.Outcome } - return false + return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED +} + +func (x *DeleteSandboxResponse) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" } // Create SSH session request. @@ -4443,6 +4536,7 @@ type DeleteServiceRequest struct { Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` // Explicit workspace scope. The all-workspaces selection is invalid. WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + AllowMissing bool `protobuf:"varint,5,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4498,11 +4592,17 @@ func (x *DeleteServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelecto return nil } +func (x *DeleteServiceRequest) GetAllowMissing() bool { + if x != nil { + return x.AllowMissing + } + return false +} + // Response for deleting an exposed sandbox service endpoint. type DeleteServiceResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // True when an endpoint existed and was deleted. - Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4537,11 +4637,11 @@ func (*DeleteServiceResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{62} } -func (x *DeleteServiceResponse) GetDeleted() bool { +func (x *DeleteServiceResponse) GetOutcome() DeletionOutcome { if x != nil { - return x.Deleted + return x.Outcome } - return false + return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED } // Persisted sandbox service endpoint. @@ -4692,7 +4792,10 @@ func (x *ServiceEndpointResponse) GetUrl() string { type RevokeSshSessionRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Session token to revoke. - Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + // A missing token is NOT_FOUND unless this is true. Revoking an existing, + // already-revoked session succeeds with COMPLETED. + AllowMissing bool `protobuf:"varint,2,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4734,11 +4837,17 @@ func (x *RevokeSshSessionRequest) GetToken() string { return "" } +func (x *RevokeSshSessionRequest) GetAllowMissing() bool { + if x != nil { + return x.AllowMissing + } + return false +} + // Revoke SSH session response. type RevokeSshSessionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // True when a session was revoked. - Revoked bool `protobuf:"varint,1,opt,name=revoked,proto3" json:"revoked,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4773,11 +4882,11 @@ func (*RevokeSshSessionResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{66} } -func (x *RevokeSshSessionResponse) GetRevoked() bool { +func (x *RevokeSshSessionResponse) GetOutcome() DeletionOutcome { if x != nil { - return x.Revoked + return x.Outcome } - return false + return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED } // Execute command request. @@ -6237,6 +6346,7 @@ type DeleteProviderRequest struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Explicit workspace scope. The all-workspaces selection is invalid. WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + AllowMissing bool `protobuf:"varint,4,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -6285,6 +6395,13 @@ func (x *DeleteProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelect return nil } +func (x *DeleteProviderRequest) GetAllowMissing() bool { + if x != nil { + return x.AllowMissing + } + return false +} + // Provider response. type ProviderResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -7797,6 +7914,7 @@ type DeleteProviderRefreshRequest struct { CredentialKey string `protobuf:"bytes,2,opt,name=credential_key,json=credentialKey,proto3" json:"credential_key,omitempty"` // Explicit workspace scope. The all-workspaces selection is invalid. WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + AllowMissing bool `protobuf:"varint,5,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -7852,9 +7970,16 @@ func (x *DeleteProviderRefreshRequest) GetWorkspaceScope() *datamodelv1.Workspac return nil } +func (x *DeleteProviderRefreshRequest) GetAllowMissing() bool { + if x != nil { + return x.AllowMissing + } + return false +} + type DeleteProviderRefreshResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -7889,11 +8014,11 @@ func (*DeleteProviderRefreshResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{108} } -func (x *DeleteProviderRefreshResponse) GetDeleted() bool { +func (x *DeleteProviderRefreshResponse) GetOutcome() DeletionOutcome { if x != nil { - return x.Deleted + return x.Outcome } - return false + return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED } // Provider type profile metadata exposed to clients. @@ -8508,7 +8633,7 @@ func (x *LintProviderProfilesResponse) GetValid() bool { // Delete provider response. type DeleteProviderResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -8543,11 +8668,11 @@ func (*DeleteProviderResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{118} } -func (x *DeleteProviderResponse) GetDeleted() bool { +func (x *DeleteProviderResponse) GetOutcome() DeletionOutcome { if x != nil { - return x.Deleted + return x.Outcome } - return false + return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED } // Delete custom provider profile request. @@ -8557,6 +8682,7 @@ type DeleteProviderProfileRequest struct { // Workspace scope. When set, targets workspace-scoped profile. When empty, // targets platform-scoped profile. Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + AllowMissing bool `protobuf:"varint,3,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -8605,10 +8731,17 @@ func (x *DeleteProviderProfileRequest) GetWorkspace() string { return "" } +func (x *DeleteProviderProfileRequest) GetAllowMissing() bool { + if x != nil { + return x.AllowMissing + } + return false +} + // Delete custom provider profile response. type DeleteProviderProfileResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -8643,11 +8776,11 @@ func (*DeleteProviderProfileResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{120} } -func (x *DeleteProviderProfileResponse) GetDeleted() bool { +func (x *DeleteProviderProfileResponse) GetOutcome() DeletionOutcome { if x != nil { - return x.Deleted + return x.Outcome } - return false + return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED } // Get sandbox provider environment request. @@ -13893,6 +14026,7 @@ type DeleteWorkspaceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Workspace name (canonical lookup key). Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + AllowMissing bool `protobuf:"varint,2,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -13934,10 +14068,17 @@ func (x *DeleteWorkspaceRequest) GetName() string { return "" } +func (x *DeleteWorkspaceRequest) GetAllowMissing() bool { + if x != nil { + return x.AllowMissing + } + return false +} + // Delete workspace response. type DeleteWorkspaceResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -13972,11 +14113,11 @@ func (*DeleteWorkspaceResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{198} } -func (x *DeleteWorkspaceResponse) GetDeleted() bool { +func (x *DeleteWorkspaceResponse) GetOutcome() DeletionOutcome { if x != nil { - return x.Deleted + return x.Outcome } - return false + return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED } // Workspace membership record. @@ -14158,6 +14299,7 @@ type RemoveWorkspaceMemberRequest struct { Workspace string `protobuf:"bytes,1,opt,name=workspace,proto3" json:"workspace,omitempty"` // OIDC subject claim identifying the principal to remove. PrincipalSubject string `protobuf:"bytes,2,opt,name=principal_subject,json=principalSubject,proto3" json:"principal_subject,omitempty"` + AllowMissing bool `protobuf:"varint,3,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -14206,10 +14348,17 @@ func (x *RemoveWorkspaceMemberRequest) GetPrincipalSubject() string { return "" } +func (x *RemoveWorkspaceMemberRequest) GetAllowMissing() bool { + if x != nil { + return x.AllowMissing + } + return false +} + // Remove workspace member response. type RemoveWorkspaceMemberResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Removed bool `protobuf:"varint,1,opt,name=removed,proto3" json:"removed,omitempty"` + Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -14244,11 +14393,11 @@ func (*RemoveWorkspaceMemberResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{203} } -func (x *RemoveWorkspaceMemberResponse) GetRemoved() bool { +func (x *RemoveWorkspaceMemberResponse) GetOutcome() DeletionOutcome { if x != nil { - return x.Removed + return x.Outcome } - return false + return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED } // List workspace members request. @@ -14915,17 +15064,18 @@ const file_openshell_proto_rawDesc = "" + "\n" + "page_token\x18\x02 \x01(\tR\tpageToken\x12%\n" + "\x0elabel_selector\x18\x05 \x01(\tR\rlabelSelector\x12R\n" + - "\x0fworkspace_scope\x18\x06 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x03\x10\x04J\x04\b\x04\x10\x05R\tworkspaceR\x0eall_workspaces\"\x97\x01\n" + + "\x0fworkspace_scope\x18\x06 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x03\x10\x04J\x04\b\x04\x10\x05R\tworkspaceR\x0eall_workspaces\"\xbc\x01\n" + "\x1cDeleteSandboxTemplateRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12R\n" + - "\x0fworkspace_scope\x18\x03 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x02\x10\x03R\tworkspace\"\\\n" + + "\x0fworkspace_scope\x18\x03 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x12#\n" + + "\rallow_missing\x18\x04 \x01(\bR\fallowMissingJ\x04\b\x02\x10\x03R\tworkspace\"\\\n" + "\x17SandboxTemplateResponse\x12A\n" + "\btemplate\x18\x01 \x01(\v2%.openshell.v1.SandboxWorkloadTemplateR\btemplate\"\x8b\x01\n" + "\x1cListSandboxTemplatesResponse\x12C\n" + "\ttemplates\x18\x01 \x03(\v2%.openshell.v1.SandboxWorkloadTemplateR\ttemplates\x12&\n" + - "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"9\n" + - "\x1dDeleteSandboxTemplateResponse\x12\x18\n" + - "\adeleted\x18\x01 \x01(\bR\adeleted\"\xbf\x01\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"g\n" + + "\x1dDeleteSandboxTemplateResponse\x127\n" + + "\aoutcome\x18\x02 \x01(\x0e2\x1d.openshell.v1.DeletionOutcomeR\aoutcomeJ\x04\b\x01\x10\x02R\adeleted\"\xbf\x01\n" + "\x1cBeginRootfsTarStagingRequest\x12\x1b\n" + "\tfile_name\x18\x02 \x01(\tR\bfileName\x12\x1d\n" + "\n" + @@ -14958,10 +15108,11 @@ const file_openshell_proto_rawDesc = "" + "\fsandbox_name\x18\x01 \x01(\tR\vsandboxName\x12#\n" + "\rprovider_name\x18\x02 \x01(\tR\fproviderName\x12:\n" + "\x19expected_resource_version\x18\x03 \x01(\x04R\x17expectedResourceVersion\x12R\n" + - "\x0fworkspace_scope\x18\x05 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x04\x10\x05R\tworkspace\"\x8f\x01\n" + + "\x0fworkspace_scope\x18\x05 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x04\x10\x05R\tworkspace\"\xb4\x01\n" + "\x14DeleteSandboxRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12R\n" + - "\x0fworkspace_scope\x18\x03 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x02\x10\x03R\tworkspace\"\x8d\x01\n" + + "\x0fworkspace_scope\x18\x03 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x12#\n" + + "\rallow_missing\x18\x04 \x01(\bR\fallowMissingJ\x04\b\x02\x10\x03R\tworkspace\"\x8d\x01\n" + "\x12StopSandboxRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12R\n" + "\x0fworkspace_scope\x18\x03 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x02\x10\x03R\tworkspace\"\x8e\x01\n" + @@ -14980,9 +15131,11 @@ const file_openshell_proto_rawDesc = "" + "\battached\x18\x02 \x01(\bR\battached\"l\n" + "\x1dDetachSandboxProviderResponse\x12/\n" + "\asandbox\x18\x01 \x01(\v2\x15.openshell.v1.SandboxR\asandbox\x12\x1a\n" + - "\bdetached\x18\x02 \x01(\bR\bdetached\"1\n" + - "\x15DeleteSandboxResponse\x12\x18\n" + - "\adeleted\x18\x01 \x01(\bR\adeleted\"8\n" + + "\bdetached\x18\x02 \x01(\bR\bdetached\"~\n" + + "\x15DeleteSandboxResponse\x127\n" + + "\aoutcome\x18\x02 \x01(\x0e2\x1d.openshell.v1.DeletionOutcomeR\aoutcome\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x03 \x01(\tR\tsandboxIdJ\x04\b\x01\x10\x02R\adeleted\"8\n" + "\x17CreateSshSessionRequest\x12\x1d\n" + "\n" + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\"\xce\x02\n" + @@ -15014,13 +15167,14 @@ const file_openshell_proto_rawDesc = "" + "\x0fworkspace_scope\x18\x06 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x04\x10\x05J\x04\b\x05\x10\x06R\tworkspaceR\x0eall_workspaces\"\x81\x01\n" + "\x14ListServicesResponse\x12A\n" + "\bservices\x18\x01 \x03(\v2%.openshell.v1.ServiceEndpointResponseR\bservices\x12&\n" + - "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xaf\x01\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xd4\x01\n" + "\x14DeleteServiceRequest\x12\x18\n" + "\asandbox\x18\x01 \x01(\tR\asandbox\x12\x18\n" + "\aservice\x18\x02 \x01(\tR\aservice\x12R\n" + - "\x0fworkspace_scope\x18\x04 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x03\x10\x04R\tworkspace\"1\n" + - "\x15DeleteServiceResponse\x12\x18\n" + - "\adeleted\x18\x01 \x01(\bR\adeleted\"\xef\x01\n" + + "\x0fworkspace_scope\x18\x04 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x12#\n" + + "\rallow_missing\x18\x05 \x01(\bR\fallowMissingJ\x04\b\x03\x10\x04R\tworkspace\"_\n" + + "\x15DeleteServiceResponse\x127\n" + + "\aoutcome\x18\x02 \x01(\x0e2\x1d.openshell.v1.DeletionOutcomeR\aoutcomeJ\x04\b\x01\x10\x02R\adeleted\"\xef\x01\n" + "\x0fServiceEndpoint\x12>\n" + "\bmetadata\x18\x01 \x01(\v2\".openshell.datamodel.v1.ObjectMetaR\bmetadata\x12\x1d\n" + "\n" + @@ -15032,11 +15186,12 @@ const file_openshell_proto_rawDesc = "" + "\x06domain\x18\x06 \x01(\bR\x06domain\"f\n" + "\x17ServiceEndpointResponse\x129\n" + "\bendpoint\x18\x01 \x01(\v2\x1d.openshell.v1.ServiceEndpointR\bendpoint\x12\x10\n" + - "\x03url\x18\x02 \x01(\tR\x03url\"5\n" + + "\x03url\x18\x02 \x01(\tR\x03url\"Z\n" + "\x17RevokeSshSessionRequest\x12\x1a\n" + - "\x05token\x18\x01 \x01(\tB\x04\x88\xb5\x18\x01R\x05token\"4\n" + - "\x18RevokeSshSessionResponse\x12\x18\n" + - "\arevoked\x18\x01 \x01(\bR\arevoked\"\xd1\x03\n" + + "\x05token\x18\x01 \x01(\tB\x04\x88\xb5\x18\x01R\x05token\x12#\n" + + "\rallow_missing\x18\x02 \x01(\bR\fallowMissing\"b\n" + + "\x18RevokeSshSessionResponse\x127\n" + + "\aoutcome\x18\x02 \x01(\x0e2\x1d.openshell.v1.DeletionOutcomeR\aoutcomeJ\x04\b\x01\x10\x02R\arevoked\"\xd1\x03\n" + "\x12ExecSandboxRequest\x12\x1d\n" + "\n" + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x18\n" + @@ -15149,10 +15304,11 @@ const file_openshell_proto_rawDesc = "" + "\x0fworkspace_scope\x18\x04 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x1ah\n" + "\x1eCredentialExpirationTimesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x120\n" + - "\x05value\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x05value:\x028\x01J\x04\b\x03\x10\x04J\x04\b\x02\x10\x03R\tworkspaceR\x18credential_expires_at_ms\"\x90\x01\n" + + "\x05value\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x05value:\x028\x01J\x04\b\x03\x10\x04J\x04\b\x02\x10\x03R\tworkspaceR\x18credential_expires_at_ms\"\xb5\x01\n" + "\x15DeleteProviderRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12R\n" + - "\x0fworkspace_scope\x18\x03 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x02\x10\x03R\tworkspace\"P\n" + + "\x0fworkspace_scope\x18\x03 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x12#\n" + + "\rallow_missing\x18\x04 \x01(\bR\fallowMissingJ\x04\b\x02\x10\x03R\tworkspace\"P\n" + "\x10ProviderResponse\x12<\n" + "\bprovider\x18\x01 \x01(\v2 .openshell.datamodel.v1.ProviderR\bprovider\"\x7f\n" + "\x15ListProvidersResponse\x12>\n" + @@ -15278,13 +15434,14 @@ const file_openshell_proto_rawDesc = "" + "\x0ecredential_key\x18\x02 \x01(\tR\rcredentialKey\x12R\n" + "\x0fworkspace_scope\x18\x04 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x03\x10\x04R\tworkspace\"i\n" + " RotateProviderCredentialResponse\x12E\n" + - "\x06status\x18\x01 \x01(\v2-.openshell.v1.ProviderCredentialRefreshStatusR\x06status\"\xc6\x01\n" + + "\x06status\x18\x01 \x01(\v2-.openshell.v1.ProviderCredentialRefreshStatusR\x06status\"\xeb\x01\n" + "\x1cDeleteProviderRefreshRequest\x12\x1a\n" + "\bprovider\x18\x01 \x01(\tR\bprovider\x12%\n" + "\x0ecredential_key\x18\x02 \x01(\tR\rcredentialKey\x12R\n" + - "\x0fworkspace_scope\x18\x04 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x03\x10\x04R\tworkspace\"9\n" + - "\x1dDeleteProviderRefreshResponse\x12\x18\n" + - "\adeleted\x18\x01 \x01(\bR\adeleted\"\xd8\x05\n" + + "\x0fworkspace_scope\x18\x04 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x12#\n" + + "\rallow_missing\x18\x05 \x01(\bR\fallowMissingJ\x04\b\x03\x10\x04R\tworkspace\"g\n" + + "\x1dDeleteProviderRefreshResponse\x127\n" + + "\aoutcome\x18\x02 \x01(\x0e2\x1d.openshell.v1.DeletionOutcomeR\aoutcomeJ\x04\b\x01\x10\x02R\adeleted\"\xd8\x05\n" + "\x0fProviderProfile\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12!\n" + "\fdisplay_name\x18\x02 \x01(\tR\vdisplayName\x12 \n" + @@ -15329,14 +15486,15 @@ const file_openshell_proto_rawDesc = "" + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"\x7f\n" + "\x1cLintProviderProfilesResponse\x12I\n" + "\vdiagnostics\x18\x01 \x03(\v2'.openshell.v1.ProviderProfileDiagnosticR\vdiagnostics\x12\x14\n" + - "\x05valid\x18\x02 \x01(\bR\x05valid\"2\n" + - "\x16DeleteProviderResponse\x12\x18\n" + - "\adeleted\x18\x01 \x01(\bR\adeleted\"L\n" + + "\x05valid\x18\x02 \x01(\bR\x05valid\"`\n" + + "\x16DeleteProviderResponse\x127\n" + + "\aoutcome\x18\x02 \x01(\x0e2\x1d.openshell.v1.DeletionOutcomeR\aoutcomeJ\x04\b\x01\x10\x02R\adeleted\"q\n" + "\x1cDeleteProviderProfileRequest\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + - "\tworkspace\x18\x02 \x01(\tR\tworkspace\"9\n" + - "\x1dDeleteProviderProfileResponse\x12\x18\n" + - "\adeleted\x18\x01 \x01(\bR\adeleted\"\x94\x01\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\x12#\n" + + "\rallow_missing\x18\x03 \x01(\bR\fallowMissing\"g\n" + + "\x1dDeleteProviderProfileResponse\x127\n" + + "\aoutcome\x18\x02 \x01(\x0e2\x1d.openshell.v1.DeletionOutcomeR\aoutcomeJ\x04\b\x01\x10\x02R\adeleted\"\x94\x01\n" + "$GetSandboxProviderEnvironmentRequest\x12\x1d\n" + "\n" + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12M\n" + @@ -15743,11 +15901,12 @@ const file_openshell_proto_rawDesc = "" + "\n" + "workspaces\x18\x01 \x03(\v2!.openshell.datamodel.v1.WorkspaceR\n" + "workspaces\x12&\n" + - "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\",\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"Q\n" + "\x16DeleteWorkspaceRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\"3\n" + - "\x17DeleteWorkspaceResponse\x12\x18\n" + - "\adeleted\x18\x01 \x01(\bR\adeleted\"\xaf\x01\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12#\n" + + "\rallow_missing\x18\x02 \x01(\bR\fallowMissing\"a\n" + + "\x17DeleteWorkspaceResponse\x127\n" + + "\aoutcome\x18\x02 \x01(\x0e2\x1d.openshell.v1.DeletionOutcomeR\aoutcomeJ\x04\b\x01\x10\x02R\adeleted\"\xaf\x01\n" + "\x0fWorkspaceMember\x12>\n" + "\bmetadata\x18\x01 \x01(\v2\".openshell.datamodel.v1.ObjectMetaR\bmetadata\x12+\n" + "\x11principal_subject\x18\x02 \x01(\tR\x10principalSubject\x12/\n" + @@ -15757,12 +15916,13 @@ const file_openshell_proto_rawDesc = "" + "\x11principal_subject\x18\x02 \x01(\tR\x10principalSubject\x12/\n" + "\x04role\x18\x03 \x01(\x0e2\x1b.openshell.v1.WorkspaceRoleR\x04role\"S\n" + "\x1aAddWorkspaceMemberResponse\x125\n" + - "\x06member\x18\x01 \x01(\v2\x1d.openshell.v1.WorkspaceMemberR\x06member\"i\n" + + "\x06member\x18\x01 \x01(\v2\x1d.openshell.v1.WorkspaceMemberR\x06member\"\x8e\x01\n" + "\x1cRemoveWorkspaceMemberRequest\x12\x1c\n" + "\tworkspace\x18\x01 \x01(\tR\tworkspace\x12+\n" + - "\x11principal_subject\x18\x02 \x01(\tR\x10principalSubject\"9\n" + - "\x1dRemoveWorkspaceMemberResponse\x12\x18\n" + - "\aremoved\x18\x01 \x01(\bR\aremoved\"w\n" + + "\x11principal_subject\x18\x02 \x01(\tR\x10principalSubject\x12#\n" + + "\rallow_missing\x18\x03 \x01(\bR\fallowMissing\"g\n" + + "\x1dRemoveWorkspaceMemberResponse\x127\n" + + "\aoutcome\x18\x02 \x01(\x0e2\x1d.openshell.v1.DeletionOutcomeR\aoutcomeJ\x04\b\x01\x10\x02R\aremoved\"w\n" + "\x1bListWorkspaceMembersRequest\x12\x1c\n" + "\tworkspace\x18\x01 \x01(\tR\tworkspace\x12\x1b\n" + "\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12\x1d\n" + @@ -15851,7 +16011,12 @@ const file_openshell_proto_rawDesc = "" + "1PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_RETRY\x10\x01\x12;\n" + "7PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_REAUTHORIZE\x10\x02\x12A\n" + "=PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_FIX_CONFIGURATION\x10\x03\x12;\n" + - "7PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_INVESTIGATE\x10\x04*\xc3\x02\n" + + "7PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_INVESTIGATE\x10\x04*\x97\x01\n" + + "\x0fDeletionOutcome\x12 \n" + + "\x1cDELETION_OUTCOME_UNSPECIFIED\x10\x00\x12\x1e\n" + + "\x1aDELETION_OUTCOME_COMPLETED\x10\x01\x12\x1d\n" + + "\x19DELETION_OUTCOME_ACCEPTED\x10\x02\x12#\n" + + "\x1fDELETION_OUTCOME_ALREADY_ABSENT\x10\x03*\xc3\x02\n" + "\x0eEndpointResult\x12\x1f\n" + "\x1bENDPOINT_RESULT_UNSPECIFIED\x10\x00\x12(\n" + "$ENDPOINT_RESULT_NO_OBSERVED_EXCHANGE\x10\x01\x12*\n" + @@ -16028,7 +16193,7 @@ func file_openshell_proto_rawDescGZIP() []byte { return file_openshell_proto_rawDescData } -var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 9) +var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 10) var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 232) var file_openshell_proto_goTypes = []any{ (SandboxPhase)(0), // 0: openshell.v1.SandboxPhase @@ -16039,658 +16204,668 @@ var file_openshell_proto_goTypes = []any{ (ServiceStatus)(0), // 5: openshell.v1.ServiceStatus (WorkspaceRole)(0), // 6: openshell.v1.WorkspaceRole (ProviderCredentialRefreshRecoveryAction)(0), // 7: openshell.v1.ProviderCredentialRefreshRecoveryAction - (EndpointResult)(0), // 8: openshell.v1.EndpointResult - (*IssueSandboxTokenRequest)(nil), // 9: openshell.v1.IssueSandboxTokenRequest - (*IssueSandboxTokenResponse)(nil), // 10: openshell.v1.IssueSandboxTokenResponse - (*RefreshSandboxTokenRequest)(nil), // 11: openshell.v1.RefreshSandboxTokenRequest - (*RefreshSandboxTokenResponse)(nil), // 12: openshell.v1.RefreshSandboxTokenResponse - (*HealthRequest)(nil), // 13: openshell.v1.HealthRequest - (*HealthResponse)(nil), // 14: openshell.v1.HealthResponse - (*GetCurrentUserRequest)(nil), // 15: openshell.v1.GetCurrentUserRequest - (*GetCurrentUserResponse)(nil), // 16: openshell.v1.GetCurrentUserResponse - (*GetGatewayInfoRequest)(nil), // 17: openshell.v1.GetGatewayInfoRequest - (*GetGatewayInfoResponse)(nil), // 18: openshell.v1.GetGatewayInfoResponse - (*ComputeDriverInfo)(nil), // 19: openshell.v1.ComputeDriverInfo - (*ComputeDriverCapabilities)(nil), // 20: openshell.v1.ComputeDriverCapabilities - (*ResourceCapabilities)(nil), // 21: openshell.v1.ResourceCapabilities - (*CpuResourceCapabilities)(nil), // 22: openshell.v1.CpuResourceCapabilities - (*MemoryResourceCapabilities)(nil), // 23: openshell.v1.MemoryResourceCapabilities - (*GpuResourceCapabilities)(nil), // 24: openshell.v1.GpuResourceCapabilities - (*Sandbox)(nil), // 25: openshell.v1.Sandbox - (*SandboxSpec)(nil), // 26: openshell.v1.SandboxSpec - (*ResourceRequirements)(nil), // 27: openshell.v1.ResourceRequirements - (*GpuResourceRequirements)(nil), // 28: openshell.v1.GpuResourceRequirements - (*SandboxTemplate)(nil), // 29: openshell.v1.SandboxTemplate - (*SandboxWorkloadTemplate)(nil), // 30: openshell.v1.SandboxWorkloadTemplate - (*SandboxWorkloadTemplateSpec)(nil), // 31: openshell.v1.SandboxWorkloadTemplateSpec - (*SandboxWorkloadConfig)(nil), // 32: openshell.v1.SandboxWorkloadConfig - (*SandboxResources)(nil), // 33: openshell.v1.SandboxResources - (*SandboxServiceLevel)(nil), // 34: openshell.v1.SandboxServiceLevel - (*SandboxStartup)(nil), // 35: openshell.v1.SandboxStartup - (*SandboxWorkloadTemplateProvenance)(nil), // 36: openshell.v1.SandboxWorkloadTemplateProvenance - (*SandboxStatus)(nil), // 37: openshell.v1.SandboxStatus - (*SandboxCondition)(nil), // 38: openshell.v1.SandboxCondition - (*PlatformEvent)(nil), // 39: openshell.v1.PlatformEvent - (*CreateSandboxRequest)(nil), // 40: openshell.v1.CreateSandboxRequest - (*CreateSandboxTemplateRequest)(nil), // 41: openshell.v1.CreateSandboxTemplateRequest - (*GetSandboxTemplateRequest)(nil), // 42: openshell.v1.GetSandboxTemplateRequest - (*ListSandboxTemplatesRequest)(nil), // 43: openshell.v1.ListSandboxTemplatesRequest - (*DeleteSandboxTemplateRequest)(nil), // 44: openshell.v1.DeleteSandboxTemplateRequest - (*SandboxTemplateResponse)(nil), // 45: openshell.v1.SandboxTemplateResponse - (*ListSandboxTemplatesResponse)(nil), // 46: openshell.v1.ListSandboxTemplatesResponse - (*DeleteSandboxTemplateResponse)(nil), // 47: openshell.v1.DeleteSandboxTemplateResponse - (*BeginRootfsTarStagingRequest)(nil), // 48: openshell.v1.BeginRootfsTarStagingRequest - (*BeginRootfsTarStagingResponse)(nil), // 49: openshell.v1.BeginRootfsTarStagingResponse - (*GetSandboxRequest)(nil), // 50: openshell.v1.GetSandboxRequest - (*ListSandboxesRequest)(nil), // 51: openshell.v1.ListSandboxesRequest - (*ListSandboxProvidersRequest)(nil), // 52: openshell.v1.ListSandboxProvidersRequest - (*AttachSandboxProviderRequest)(nil), // 53: openshell.v1.AttachSandboxProviderRequest - (*DetachSandboxProviderRequest)(nil), // 54: openshell.v1.DetachSandboxProviderRequest - (*DeleteSandboxRequest)(nil), // 55: openshell.v1.DeleteSandboxRequest - (*StopSandboxRequest)(nil), // 56: openshell.v1.StopSandboxRequest - (*StartSandboxRequest)(nil), // 57: openshell.v1.StartSandboxRequest - (*SandboxResponse)(nil), // 58: openshell.v1.SandboxResponse - (*ListSandboxesResponse)(nil), // 59: openshell.v1.ListSandboxesResponse - (*ListSandboxProvidersResponse)(nil), // 60: openshell.v1.ListSandboxProvidersResponse - (*AttachSandboxProviderResponse)(nil), // 61: openshell.v1.AttachSandboxProviderResponse - (*DetachSandboxProviderResponse)(nil), // 62: openshell.v1.DetachSandboxProviderResponse - (*DeleteSandboxResponse)(nil), // 63: openshell.v1.DeleteSandboxResponse - (*CreateSshSessionRequest)(nil), // 64: openshell.v1.CreateSshSessionRequest - (*CreateSshSessionResponse)(nil), // 65: openshell.v1.CreateSshSessionResponse - (*ExposeServiceRequest)(nil), // 66: openshell.v1.ExposeServiceRequest - (*GetServiceRequest)(nil), // 67: openshell.v1.GetServiceRequest - (*ListServicesRequest)(nil), // 68: openshell.v1.ListServicesRequest - (*ListServicesResponse)(nil), // 69: openshell.v1.ListServicesResponse - (*DeleteServiceRequest)(nil), // 70: openshell.v1.DeleteServiceRequest - (*DeleteServiceResponse)(nil), // 71: openshell.v1.DeleteServiceResponse - (*ServiceEndpoint)(nil), // 72: openshell.v1.ServiceEndpoint - (*ServiceEndpointResponse)(nil), // 73: openshell.v1.ServiceEndpointResponse - (*RevokeSshSessionRequest)(nil), // 74: openshell.v1.RevokeSshSessionRequest - (*RevokeSshSessionResponse)(nil), // 75: openshell.v1.RevokeSshSessionResponse - (*ExecSandboxRequest)(nil), // 76: openshell.v1.ExecSandboxRequest - (*ExecSandboxStdout)(nil), // 77: openshell.v1.ExecSandboxStdout - (*ExecSandboxStderr)(nil), // 78: openshell.v1.ExecSandboxStderr - (*ExecSandboxExit)(nil), // 79: openshell.v1.ExecSandboxExit - (*ExecSandboxEvent)(nil), // 80: openshell.v1.ExecSandboxEvent - (*TcpForwardInit)(nil), // 81: openshell.v1.TcpForwardInit - (*TcpForwardFrame)(nil), // 82: openshell.v1.TcpForwardFrame - (*ExecSandboxInput)(nil), // 83: openshell.v1.ExecSandboxInput - (*ExecSandboxWindowResize)(nil), // 84: openshell.v1.ExecSandboxWindowResize - (*SshSession)(nil), // 85: openshell.v1.SshSession - (*WatchSandboxRequest)(nil), // 86: openshell.v1.WatchSandboxRequest - (*SandboxStreamEvent)(nil), // 87: openshell.v1.SandboxStreamEvent - (*SandboxLogLine)(nil), // 88: openshell.v1.SandboxLogLine - (*SandboxStreamWarning)(nil), // 89: openshell.v1.SandboxStreamWarning - (*CreateProviderRequest)(nil), // 90: openshell.v1.CreateProviderRequest - (*GetProviderRequest)(nil), // 91: openshell.v1.GetProviderRequest - (*ListProvidersRequest)(nil), // 92: openshell.v1.ListProvidersRequest - (*UpdateProviderRequest)(nil), // 93: openshell.v1.UpdateProviderRequest - (*DeleteProviderRequest)(nil), // 94: openshell.v1.DeleteProviderRequest - (*ProviderResponse)(nil), // 95: openshell.v1.ProviderResponse - (*ListProvidersResponse)(nil), // 96: openshell.v1.ListProvidersResponse - (*ListProviderProfilesRequest)(nil), // 97: openshell.v1.ListProviderProfilesRequest - (*GetProviderProfileRequest)(nil), // 98: openshell.v1.GetProviderProfileRequest - (*ProviderProfileImportItem)(nil), // 99: openshell.v1.ProviderProfileImportItem - (*ProviderProfileDiagnostic)(nil), // 100: openshell.v1.ProviderProfileDiagnostic - (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 101: openshell.v1.ProviderCredentialTokenGrantAudienceOverride - (*ProviderCredentialTokenGrantSubjectToken)(nil), // 102: openshell.v1.ProviderCredentialTokenGrantSubjectToken - (*ProviderCredentialTokenGrant)(nil), // 103: openshell.v1.ProviderCredentialTokenGrant - (*ProviderProfileCredential)(nil), // 104: openshell.v1.ProviderProfileCredential - (*ProviderCredentialRefreshMaterial)(nil), // 105: openshell.v1.ProviderCredentialRefreshMaterial - (*ProviderCredentialRefreshOutput)(nil), // 106: openshell.v1.ProviderCredentialRefreshOutput - (*ProviderCredentialRefresh)(nil), // 107: openshell.v1.ProviderCredentialRefresh - (*ProviderCredentialRefreshStatus)(nil), // 108: openshell.v1.ProviderCredentialRefreshStatus - (*ProviderProfileDiscovery)(nil), // 109: openshell.v1.ProviderProfileDiscovery - (*GetProviderRefreshStatusRequest)(nil), // 110: openshell.v1.GetProviderRefreshStatusRequest - (*GetProviderRefreshStatusResponse)(nil), // 111: openshell.v1.GetProviderRefreshStatusResponse - (*ConfigureProviderRefreshRequest)(nil), // 112: openshell.v1.ConfigureProviderRefreshRequest - (*ConfigureProviderRefreshResponse)(nil), // 113: openshell.v1.ConfigureProviderRefreshResponse - (*RotateProviderCredentialRequest)(nil), // 114: openshell.v1.RotateProviderCredentialRequest - (*RotateProviderCredentialResponse)(nil), // 115: openshell.v1.RotateProviderCredentialResponse - (*DeleteProviderRefreshRequest)(nil), // 116: openshell.v1.DeleteProviderRefreshRequest - (*DeleteProviderRefreshResponse)(nil), // 117: openshell.v1.DeleteProviderRefreshResponse - (*ProviderProfile)(nil), // 118: openshell.v1.ProviderProfile - (*ProviderProfileResponse)(nil), // 119: openshell.v1.ProviderProfileResponse - (*ListProviderProfilesResponse)(nil), // 120: openshell.v1.ListProviderProfilesResponse - (*ImportProviderProfilesRequest)(nil), // 121: openshell.v1.ImportProviderProfilesRequest - (*ImportProviderProfilesResponse)(nil), // 122: openshell.v1.ImportProviderProfilesResponse - (*UpdateProviderProfilesRequest)(nil), // 123: openshell.v1.UpdateProviderProfilesRequest - (*UpdateProviderProfilesResponse)(nil), // 124: openshell.v1.UpdateProviderProfilesResponse - (*LintProviderProfilesRequest)(nil), // 125: openshell.v1.LintProviderProfilesRequest - (*LintProviderProfilesResponse)(nil), // 126: openshell.v1.LintProviderProfilesResponse - (*DeleteProviderResponse)(nil), // 127: openshell.v1.DeleteProviderResponse - (*DeleteProviderProfileRequest)(nil), // 128: openshell.v1.DeleteProviderProfileRequest - (*DeleteProviderProfileResponse)(nil), // 129: openshell.v1.DeleteProviderProfileResponse - (*GetSandboxProviderEnvironmentRequest)(nil), // 130: openshell.v1.GetSandboxProviderEnvironmentRequest - (*StaticCredentialEndpointBinding)(nil), // 131: openshell.v1.StaticCredentialEndpointBinding - (*StaticCredentialBinding)(nil), // 132: openshell.v1.StaticCredentialBinding - (*GetSandboxProviderEnvironmentResponse)(nil), // 133: openshell.v1.GetSandboxProviderEnvironmentResponse - (*ExchangeProviderSubjectTokenRequest)(nil), // 134: openshell.v1.ExchangeProviderSubjectTokenRequest - (*ExchangeProviderSubjectTokenResponse)(nil), // 135: openshell.v1.ExchangeProviderSubjectTokenResponse - (*UpdateConfigRequest)(nil), // 136: openshell.v1.UpdateConfigRequest - (*PolicyMergeOperation)(nil), // 137: openshell.v1.PolicyMergeOperation - (*AddNetworkRule)(nil), // 138: openshell.v1.AddNetworkRule - (*RemoveNetworkEndpoint)(nil), // 139: openshell.v1.RemoveNetworkEndpoint - (*RemoveNetworkRule)(nil), // 140: openshell.v1.RemoveNetworkRule - (*AddDenyRules)(nil), // 141: openshell.v1.AddDenyRules - (*AddAllowRules)(nil), // 142: openshell.v1.AddAllowRules - (*RemoveNetworkBinary)(nil), // 143: openshell.v1.RemoveNetworkBinary - (*UpdateConfigResponse)(nil), // 144: openshell.v1.UpdateConfigResponse - (*GetSandboxPolicyStatusRequest)(nil), // 145: openshell.v1.GetSandboxPolicyStatusRequest - (*GetSandboxPolicyStatusResponse)(nil), // 146: openshell.v1.GetSandboxPolicyStatusResponse - (*ListSandboxPoliciesRequest)(nil), // 147: openshell.v1.ListSandboxPoliciesRequest - (*ListSandboxPoliciesResponse)(nil), // 148: openshell.v1.ListSandboxPoliciesResponse - (*ReportPolicyStatusRequest)(nil), // 149: openshell.v1.ReportPolicyStatusRequest - (*ReportPolicyStatusResponse)(nil), // 150: openshell.v1.ReportPolicyStatusResponse - (*SandboxPolicyRevision)(nil), // 151: openshell.v1.SandboxPolicyRevision - (*GetSandboxLogsRequest)(nil), // 152: openshell.v1.GetSandboxLogsRequest - (*PushSandboxLogsRequest)(nil), // 153: openshell.v1.PushSandboxLogsRequest - (*PushSandboxLogsResponse)(nil), // 154: openshell.v1.PushSandboxLogsResponse - (*GetSandboxLogsResponse)(nil), // 155: openshell.v1.GetSandboxLogsResponse - (*SupervisorMessage)(nil), // 156: openshell.v1.SupervisorMessage - (*GatewayMessage)(nil), // 157: openshell.v1.GatewayMessage - (*SupervisorHello)(nil), // 158: openshell.v1.SupervisorHello - (*SessionAccepted)(nil), // 159: openshell.v1.SessionAccepted - (*SessionRejected)(nil), // 160: openshell.v1.SessionRejected - (*SupervisorHeartbeat)(nil), // 161: openshell.v1.SupervisorHeartbeat - (*GatewayHeartbeat)(nil), // 162: openshell.v1.GatewayHeartbeat - (*ReportMainProcessExitRequest)(nil), // 163: openshell.v1.ReportMainProcessExitRequest - (*ReportMainProcessExitResponse)(nil), // 164: openshell.v1.ReportMainProcessExitResponse - (*FinalizeMainProcessExitRequest)(nil), // 165: openshell.v1.FinalizeMainProcessExitRequest - (*FinalizeMainProcessExitResponse)(nil), // 166: openshell.v1.FinalizeMainProcessExitResponse - (*RelayOpen)(nil), // 167: openshell.v1.RelayOpen - (*SshRelayTarget)(nil), // 168: openshell.v1.SshRelayTarget - (*TcpRelayTarget)(nil), // 169: openshell.v1.TcpRelayTarget - (*RelayInit)(nil), // 170: openshell.v1.RelayInit - (*RelayFrame)(nil), // 171: openshell.v1.RelayFrame - (*RelayOpenResult)(nil), // 172: openshell.v1.RelayOpenResult - (*RelayClose)(nil), // 173: openshell.v1.RelayClose - (*L7RequestSample)(nil), // 174: openshell.v1.L7RequestSample - (*DenialSummary)(nil), // 175: openshell.v1.DenialSummary - (*DenialGroupCount)(nil), // 176: openshell.v1.DenialGroupCount - (*NetworkActivitySummary)(nil), // 177: openshell.v1.NetworkActivitySummary - (*PolicyChunk)(nil), // 178: openshell.v1.PolicyChunk - (*DraftPolicyUpdate)(nil), // 179: openshell.v1.DraftPolicyUpdate - (*SubmitPolicyAnalysisRequest)(nil), // 180: openshell.v1.SubmitPolicyAnalysisRequest - (*SubmitPolicyAnalysisResponse)(nil), // 181: openshell.v1.SubmitPolicyAnalysisResponse - (*GetDraftPolicyRequest)(nil), // 182: openshell.v1.GetDraftPolicyRequest - (*GetDraftPolicyResponse)(nil), // 183: openshell.v1.GetDraftPolicyResponse - (*ApproveDraftChunkRequest)(nil), // 184: openshell.v1.ApproveDraftChunkRequest - (*ApproveDraftChunkResponse)(nil), // 185: openshell.v1.ApproveDraftChunkResponse - (*RejectDraftChunkRequest)(nil), // 186: openshell.v1.RejectDraftChunkRequest - (*RejectDraftChunkResponse)(nil), // 187: openshell.v1.RejectDraftChunkResponse - (*DraftChunkApproval)(nil), // 188: openshell.v1.DraftChunkApproval - (*ApproveAllDraftChunksRequest)(nil), // 189: openshell.v1.ApproveAllDraftChunksRequest - (*ApproveAllDraftChunksResponse)(nil), // 190: openshell.v1.ApproveAllDraftChunksResponse - (*EditDraftChunkRequest)(nil), // 191: openshell.v1.EditDraftChunkRequest - (*EditDraftChunkResponse)(nil), // 192: openshell.v1.EditDraftChunkResponse - (*UndoDraftChunkRequest)(nil), // 193: openshell.v1.UndoDraftChunkRequest - (*UndoDraftChunkResponse)(nil), // 194: openshell.v1.UndoDraftChunkResponse - (*ClearDraftChunksRequest)(nil), // 195: openshell.v1.ClearDraftChunksRequest - (*ClearDraftChunksResponse)(nil), // 196: openshell.v1.ClearDraftChunksResponse - (*GetDraftHistoryRequest)(nil), // 197: openshell.v1.GetDraftHistoryRequest - (*DraftHistoryEntry)(nil), // 198: openshell.v1.DraftHistoryEntry - (*GetDraftHistoryResponse)(nil), // 199: openshell.v1.GetDraftHistoryResponse - (*CreateWorkspaceRequest)(nil), // 200: openshell.v1.CreateWorkspaceRequest - (*CreateWorkspaceResponse)(nil), // 201: openshell.v1.CreateWorkspaceResponse - (*GetWorkspaceRequest)(nil), // 202: openshell.v1.GetWorkspaceRequest - (*GetWorkspaceResponse)(nil), // 203: openshell.v1.GetWorkspaceResponse - (*ListWorkspacesRequest)(nil), // 204: openshell.v1.ListWorkspacesRequest - (*ListWorkspacesResponse)(nil), // 205: openshell.v1.ListWorkspacesResponse - (*DeleteWorkspaceRequest)(nil), // 206: openshell.v1.DeleteWorkspaceRequest - (*DeleteWorkspaceResponse)(nil), // 207: openshell.v1.DeleteWorkspaceResponse - (*WorkspaceMember)(nil), // 208: openshell.v1.WorkspaceMember - (*AddWorkspaceMemberRequest)(nil), // 209: openshell.v1.AddWorkspaceMemberRequest - (*AddWorkspaceMemberResponse)(nil), // 210: openshell.v1.AddWorkspaceMemberResponse - (*RemoveWorkspaceMemberRequest)(nil), // 211: openshell.v1.RemoveWorkspaceMemberRequest - (*RemoveWorkspaceMemberResponse)(nil), // 212: openshell.v1.RemoveWorkspaceMemberResponse - (*ListWorkspaceMembersRequest)(nil), // 213: openshell.v1.ListWorkspaceMembersRequest - (*ListWorkspaceMembersResponse)(nil), // 214: openshell.v1.ListWorkspaceMembersResponse - (*ExtensionServiceCredential)(nil), // 215: openshell.v1.ExtensionServiceCredential - (*EndpointObservation)(nil), // 216: openshell.v1.EndpointObservation - (*ReportEndpointStatusRequest)(nil), // 217: openshell.v1.ReportEndpointStatusRequest - (*ReportEndpointStatusResponse)(nil), // 218: openshell.v1.ReportEndpointStatusResponse - (*EndpointStatus)(nil), // 219: openshell.v1.EndpointStatus - nil, // 220: openshell.v1.SandboxSpec.EnvironmentEntry - nil, // 221: openshell.v1.SandboxTemplate.LabelsEntry - nil, // 222: openshell.v1.SandboxTemplate.AnnotationsEntry - nil, // 223: openshell.v1.SandboxTemplate.EnvironmentEntry - nil, // 224: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry - nil, // 225: openshell.v1.PlatformEvent.MetadataEntry - nil, // 226: openshell.v1.CreateSandboxRequest.LabelsEntry - nil, // 227: openshell.v1.CreateSandboxRequest.AnnotationsEntry - nil, // 228: openshell.v1.ExecSandboxRequest.EnvironmentEntry - nil, // 229: openshell.v1.SandboxLogLine.FieldsEntry - nil, // 230: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - nil, // 231: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - nil, // 232: openshell.v1.ProviderProfile.AnnotationsEntry - nil, // 233: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - nil, // 234: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - nil, // 235: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - nil, // 236: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - nil, // 237: openshell.v1.UpdateConfigRequest.AnnotationsEntry - nil, // 238: openshell.v1.UpdateConfigResponse.AnnotationsEntry - nil, // 239: openshell.v1.SandboxPolicyRevision.ProvenanceEntry - nil, // 240: openshell.v1.CreateWorkspaceRequest.LabelsEntry - (*timestamppb.Timestamp)(nil), // 241: google.protobuf.Timestamp - (*datamodelv1.ObjectMeta)(nil), // 242: openshell.datamodel.v1.ObjectMeta - (*sandboxv1.SandboxPolicy)(nil), // 243: openshell.sandbox.v1.SandboxPolicy - (*structpb.Struct)(nil), // 244: google.protobuf.Struct - (*durationpb.Duration)(nil), // 245: google.protobuf.Duration - (*datamodelv1.WorkspaceSelector)(nil), // 246: openshell.datamodel.v1.WorkspaceSelector - (*datamodelv1.Provider)(nil), // 247: openshell.datamodel.v1.Provider - (*sandboxv1.NetworkEndpoint)(nil), // 248: openshell.sandbox.v1.NetworkEndpoint - (*sandboxv1.NetworkBinary)(nil), // 249: openshell.sandbox.v1.NetworkBinary - (*sandboxv1.SettingValue)(nil), // 250: openshell.sandbox.v1.SettingValue - (*sandboxv1.NetworkPolicyRule)(nil), // 251: openshell.sandbox.v1.NetworkPolicyRule - (*sandboxv1.L7DenyRule)(nil), // 252: openshell.sandbox.v1.L7DenyRule - (*sandboxv1.L7Rule)(nil), // 253: openshell.sandbox.v1.L7Rule - (*datamodelv1.Workspace)(nil), // 254: openshell.datamodel.v1.Workspace - (*sandboxv1.GetSandboxConfigRequest)(nil), // 255: openshell.sandbox.v1.GetSandboxConfigRequest - (*sandboxv1.GetGatewayConfigRequest)(nil), // 256: openshell.sandbox.v1.GetGatewayConfigRequest - (*sandboxv1.GetSandboxConfigResponse)(nil), // 257: openshell.sandbox.v1.GetSandboxConfigResponse - (*sandboxv1.GetGatewayConfigResponse)(nil), // 258: openshell.sandbox.v1.GetGatewayConfigResponse + (DeletionOutcome)(0), // 8: openshell.v1.DeletionOutcome + (EndpointResult)(0), // 9: openshell.v1.EndpointResult + (*IssueSandboxTokenRequest)(nil), // 10: openshell.v1.IssueSandboxTokenRequest + (*IssueSandboxTokenResponse)(nil), // 11: openshell.v1.IssueSandboxTokenResponse + (*RefreshSandboxTokenRequest)(nil), // 12: openshell.v1.RefreshSandboxTokenRequest + (*RefreshSandboxTokenResponse)(nil), // 13: openshell.v1.RefreshSandboxTokenResponse + (*HealthRequest)(nil), // 14: openshell.v1.HealthRequest + (*HealthResponse)(nil), // 15: openshell.v1.HealthResponse + (*GetCurrentUserRequest)(nil), // 16: openshell.v1.GetCurrentUserRequest + (*GetCurrentUserResponse)(nil), // 17: openshell.v1.GetCurrentUserResponse + (*GetGatewayInfoRequest)(nil), // 18: openshell.v1.GetGatewayInfoRequest + (*GetGatewayInfoResponse)(nil), // 19: openshell.v1.GetGatewayInfoResponse + (*ComputeDriverInfo)(nil), // 20: openshell.v1.ComputeDriverInfo + (*ComputeDriverCapabilities)(nil), // 21: openshell.v1.ComputeDriverCapabilities + (*ResourceCapabilities)(nil), // 22: openshell.v1.ResourceCapabilities + (*CpuResourceCapabilities)(nil), // 23: openshell.v1.CpuResourceCapabilities + (*MemoryResourceCapabilities)(nil), // 24: openshell.v1.MemoryResourceCapabilities + (*GpuResourceCapabilities)(nil), // 25: openshell.v1.GpuResourceCapabilities + (*Sandbox)(nil), // 26: openshell.v1.Sandbox + (*SandboxSpec)(nil), // 27: openshell.v1.SandboxSpec + (*ResourceRequirements)(nil), // 28: openshell.v1.ResourceRequirements + (*GpuResourceRequirements)(nil), // 29: openshell.v1.GpuResourceRequirements + (*SandboxTemplate)(nil), // 30: openshell.v1.SandboxTemplate + (*SandboxWorkloadTemplate)(nil), // 31: openshell.v1.SandboxWorkloadTemplate + (*SandboxWorkloadTemplateSpec)(nil), // 32: openshell.v1.SandboxWorkloadTemplateSpec + (*SandboxWorkloadConfig)(nil), // 33: openshell.v1.SandboxWorkloadConfig + (*SandboxResources)(nil), // 34: openshell.v1.SandboxResources + (*SandboxServiceLevel)(nil), // 35: openshell.v1.SandboxServiceLevel + (*SandboxStartup)(nil), // 36: openshell.v1.SandboxStartup + (*SandboxWorkloadTemplateProvenance)(nil), // 37: openshell.v1.SandboxWorkloadTemplateProvenance + (*SandboxStatus)(nil), // 38: openshell.v1.SandboxStatus + (*SandboxCondition)(nil), // 39: openshell.v1.SandboxCondition + (*PlatformEvent)(nil), // 40: openshell.v1.PlatformEvent + (*CreateSandboxRequest)(nil), // 41: openshell.v1.CreateSandboxRequest + (*CreateSandboxTemplateRequest)(nil), // 42: openshell.v1.CreateSandboxTemplateRequest + (*GetSandboxTemplateRequest)(nil), // 43: openshell.v1.GetSandboxTemplateRequest + (*ListSandboxTemplatesRequest)(nil), // 44: openshell.v1.ListSandboxTemplatesRequest + (*DeleteSandboxTemplateRequest)(nil), // 45: openshell.v1.DeleteSandboxTemplateRequest + (*SandboxTemplateResponse)(nil), // 46: openshell.v1.SandboxTemplateResponse + (*ListSandboxTemplatesResponse)(nil), // 47: openshell.v1.ListSandboxTemplatesResponse + (*DeleteSandboxTemplateResponse)(nil), // 48: openshell.v1.DeleteSandboxTemplateResponse + (*BeginRootfsTarStagingRequest)(nil), // 49: openshell.v1.BeginRootfsTarStagingRequest + (*BeginRootfsTarStagingResponse)(nil), // 50: openshell.v1.BeginRootfsTarStagingResponse + (*GetSandboxRequest)(nil), // 51: openshell.v1.GetSandboxRequest + (*ListSandboxesRequest)(nil), // 52: openshell.v1.ListSandboxesRequest + (*ListSandboxProvidersRequest)(nil), // 53: openshell.v1.ListSandboxProvidersRequest + (*AttachSandboxProviderRequest)(nil), // 54: openshell.v1.AttachSandboxProviderRequest + (*DetachSandboxProviderRequest)(nil), // 55: openshell.v1.DetachSandboxProviderRequest + (*DeleteSandboxRequest)(nil), // 56: openshell.v1.DeleteSandboxRequest + (*StopSandboxRequest)(nil), // 57: openshell.v1.StopSandboxRequest + (*StartSandboxRequest)(nil), // 58: openshell.v1.StartSandboxRequest + (*SandboxResponse)(nil), // 59: openshell.v1.SandboxResponse + (*ListSandboxesResponse)(nil), // 60: openshell.v1.ListSandboxesResponse + (*ListSandboxProvidersResponse)(nil), // 61: openshell.v1.ListSandboxProvidersResponse + (*AttachSandboxProviderResponse)(nil), // 62: openshell.v1.AttachSandboxProviderResponse + (*DetachSandboxProviderResponse)(nil), // 63: openshell.v1.DetachSandboxProviderResponse + (*DeleteSandboxResponse)(nil), // 64: openshell.v1.DeleteSandboxResponse + (*CreateSshSessionRequest)(nil), // 65: openshell.v1.CreateSshSessionRequest + (*CreateSshSessionResponse)(nil), // 66: openshell.v1.CreateSshSessionResponse + (*ExposeServiceRequest)(nil), // 67: openshell.v1.ExposeServiceRequest + (*GetServiceRequest)(nil), // 68: openshell.v1.GetServiceRequest + (*ListServicesRequest)(nil), // 69: openshell.v1.ListServicesRequest + (*ListServicesResponse)(nil), // 70: openshell.v1.ListServicesResponse + (*DeleteServiceRequest)(nil), // 71: openshell.v1.DeleteServiceRequest + (*DeleteServiceResponse)(nil), // 72: openshell.v1.DeleteServiceResponse + (*ServiceEndpoint)(nil), // 73: openshell.v1.ServiceEndpoint + (*ServiceEndpointResponse)(nil), // 74: openshell.v1.ServiceEndpointResponse + (*RevokeSshSessionRequest)(nil), // 75: openshell.v1.RevokeSshSessionRequest + (*RevokeSshSessionResponse)(nil), // 76: openshell.v1.RevokeSshSessionResponse + (*ExecSandboxRequest)(nil), // 77: openshell.v1.ExecSandboxRequest + (*ExecSandboxStdout)(nil), // 78: openshell.v1.ExecSandboxStdout + (*ExecSandboxStderr)(nil), // 79: openshell.v1.ExecSandboxStderr + (*ExecSandboxExit)(nil), // 80: openshell.v1.ExecSandboxExit + (*ExecSandboxEvent)(nil), // 81: openshell.v1.ExecSandboxEvent + (*TcpForwardInit)(nil), // 82: openshell.v1.TcpForwardInit + (*TcpForwardFrame)(nil), // 83: openshell.v1.TcpForwardFrame + (*ExecSandboxInput)(nil), // 84: openshell.v1.ExecSandboxInput + (*ExecSandboxWindowResize)(nil), // 85: openshell.v1.ExecSandboxWindowResize + (*SshSession)(nil), // 86: openshell.v1.SshSession + (*WatchSandboxRequest)(nil), // 87: openshell.v1.WatchSandboxRequest + (*SandboxStreamEvent)(nil), // 88: openshell.v1.SandboxStreamEvent + (*SandboxLogLine)(nil), // 89: openshell.v1.SandboxLogLine + (*SandboxStreamWarning)(nil), // 90: openshell.v1.SandboxStreamWarning + (*CreateProviderRequest)(nil), // 91: openshell.v1.CreateProviderRequest + (*GetProviderRequest)(nil), // 92: openshell.v1.GetProviderRequest + (*ListProvidersRequest)(nil), // 93: openshell.v1.ListProvidersRequest + (*UpdateProviderRequest)(nil), // 94: openshell.v1.UpdateProviderRequest + (*DeleteProviderRequest)(nil), // 95: openshell.v1.DeleteProviderRequest + (*ProviderResponse)(nil), // 96: openshell.v1.ProviderResponse + (*ListProvidersResponse)(nil), // 97: openshell.v1.ListProvidersResponse + (*ListProviderProfilesRequest)(nil), // 98: openshell.v1.ListProviderProfilesRequest + (*GetProviderProfileRequest)(nil), // 99: openshell.v1.GetProviderProfileRequest + (*ProviderProfileImportItem)(nil), // 100: openshell.v1.ProviderProfileImportItem + (*ProviderProfileDiagnostic)(nil), // 101: openshell.v1.ProviderProfileDiagnostic + (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 102: openshell.v1.ProviderCredentialTokenGrantAudienceOverride + (*ProviderCredentialTokenGrantSubjectToken)(nil), // 103: openshell.v1.ProviderCredentialTokenGrantSubjectToken + (*ProviderCredentialTokenGrant)(nil), // 104: openshell.v1.ProviderCredentialTokenGrant + (*ProviderProfileCredential)(nil), // 105: openshell.v1.ProviderProfileCredential + (*ProviderCredentialRefreshMaterial)(nil), // 106: openshell.v1.ProviderCredentialRefreshMaterial + (*ProviderCredentialRefreshOutput)(nil), // 107: openshell.v1.ProviderCredentialRefreshOutput + (*ProviderCredentialRefresh)(nil), // 108: openshell.v1.ProviderCredentialRefresh + (*ProviderCredentialRefreshStatus)(nil), // 109: openshell.v1.ProviderCredentialRefreshStatus + (*ProviderProfileDiscovery)(nil), // 110: openshell.v1.ProviderProfileDiscovery + (*GetProviderRefreshStatusRequest)(nil), // 111: openshell.v1.GetProviderRefreshStatusRequest + (*GetProviderRefreshStatusResponse)(nil), // 112: openshell.v1.GetProviderRefreshStatusResponse + (*ConfigureProviderRefreshRequest)(nil), // 113: openshell.v1.ConfigureProviderRefreshRequest + (*ConfigureProviderRefreshResponse)(nil), // 114: openshell.v1.ConfigureProviderRefreshResponse + (*RotateProviderCredentialRequest)(nil), // 115: openshell.v1.RotateProviderCredentialRequest + (*RotateProviderCredentialResponse)(nil), // 116: openshell.v1.RotateProviderCredentialResponse + (*DeleteProviderRefreshRequest)(nil), // 117: openshell.v1.DeleteProviderRefreshRequest + (*DeleteProviderRefreshResponse)(nil), // 118: openshell.v1.DeleteProviderRefreshResponse + (*ProviderProfile)(nil), // 119: openshell.v1.ProviderProfile + (*ProviderProfileResponse)(nil), // 120: openshell.v1.ProviderProfileResponse + (*ListProviderProfilesResponse)(nil), // 121: openshell.v1.ListProviderProfilesResponse + (*ImportProviderProfilesRequest)(nil), // 122: openshell.v1.ImportProviderProfilesRequest + (*ImportProviderProfilesResponse)(nil), // 123: openshell.v1.ImportProviderProfilesResponse + (*UpdateProviderProfilesRequest)(nil), // 124: openshell.v1.UpdateProviderProfilesRequest + (*UpdateProviderProfilesResponse)(nil), // 125: openshell.v1.UpdateProviderProfilesResponse + (*LintProviderProfilesRequest)(nil), // 126: openshell.v1.LintProviderProfilesRequest + (*LintProviderProfilesResponse)(nil), // 127: openshell.v1.LintProviderProfilesResponse + (*DeleteProviderResponse)(nil), // 128: openshell.v1.DeleteProviderResponse + (*DeleteProviderProfileRequest)(nil), // 129: openshell.v1.DeleteProviderProfileRequest + (*DeleteProviderProfileResponse)(nil), // 130: openshell.v1.DeleteProviderProfileResponse + (*GetSandboxProviderEnvironmentRequest)(nil), // 131: openshell.v1.GetSandboxProviderEnvironmentRequest + (*StaticCredentialEndpointBinding)(nil), // 132: openshell.v1.StaticCredentialEndpointBinding + (*StaticCredentialBinding)(nil), // 133: openshell.v1.StaticCredentialBinding + (*GetSandboxProviderEnvironmentResponse)(nil), // 134: openshell.v1.GetSandboxProviderEnvironmentResponse + (*ExchangeProviderSubjectTokenRequest)(nil), // 135: openshell.v1.ExchangeProviderSubjectTokenRequest + (*ExchangeProviderSubjectTokenResponse)(nil), // 136: openshell.v1.ExchangeProviderSubjectTokenResponse + (*UpdateConfigRequest)(nil), // 137: openshell.v1.UpdateConfigRequest + (*PolicyMergeOperation)(nil), // 138: openshell.v1.PolicyMergeOperation + (*AddNetworkRule)(nil), // 139: openshell.v1.AddNetworkRule + (*RemoveNetworkEndpoint)(nil), // 140: openshell.v1.RemoveNetworkEndpoint + (*RemoveNetworkRule)(nil), // 141: openshell.v1.RemoveNetworkRule + (*AddDenyRules)(nil), // 142: openshell.v1.AddDenyRules + (*AddAllowRules)(nil), // 143: openshell.v1.AddAllowRules + (*RemoveNetworkBinary)(nil), // 144: openshell.v1.RemoveNetworkBinary + (*UpdateConfigResponse)(nil), // 145: openshell.v1.UpdateConfigResponse + (*GetSandboxPolicyStatusRequest)(nil), // 146: openshell.v1.GetSandboxPolicyStatusRequest + (*GetSandboxPolicyStatusResponse)(nil), // 147: openshell.v1.GetSandboxPolicyStatusResponse + (*ListSandboxPoliciesRequest)(nil), // 148: openshell.v1.ListSandboxPoliciesRequest + (*ListSandboxPoliciesResponse)(nil), // 149: openshell.v1.ListSandboxPoliciesResponse + (*ReportPolicyStatusRequest)(nil), // 150: openshell.v1.ReportPolicyStatusRequest + (*ReportPolicyStatusResponse)(nil), // 151: openshell.v1.ReportPolicyStatusResponse + (*SandboxPolicyRevision)(nil), // 152: openshell.v1.SandboxPolicyRevision + (*GetSandboxLogsRequest)(nil), // 153: openshell.v1.GetSandboxLogsRequest + (*PushSandboxLogsRequest)(nil), // 154: openshell.v1.PushSandboxLogsRequest + (*PushSandboxLogsResponse)(nil), // 155: openshell.v1.PushSandboxLogsResponse + (*GetSandboxLogsResponse)(nil), // 156: openshell.v1.GetSandboxLogsResponse + (*SupervisorMessage)(nil), // 157: openshell.v1.SupervisorMessage + (*GatewayMessage)(nil), // 158: openshell.v1.GatewayMessage + (*SupervisorHello)(nil), // 159: openshell.v1.SupervisorHello + (*SessionAccepted)(nil), // 160: openshell.v1.SessionAccepted + (*SessionRejected)(nil), // 161: openshell.v1.SessionRejected + (*SupervisorHeartbeat)(nil), // 162: openshell.v1.SupervisorHeartbeat + (*GatewayHeartbeat)(nil), // 163: openshell.v1.GatewayHeartbeat + (*ReportMainProcessExitRequest)(nil), // 164: openshell.v1.ReportMainProcessExitRequest + (*ReportMainProcessExitResponse)(nil), // 165: openshell.v1.ReportMainProcessExitResponse + (*FinalizeMainProcessExitRequest)(nil), // 166: openshell.v1.FinalizeMainProcessExitRequest + (*FinalizeMainProcessExitResponse)(nil), // 167: openshell.v1.FinalizeMainProcessExitResponse + (*RelayOpen)(nil), // 168: openshell.v1.RelayOpen + (*SshRelayTarget)(nil), // 169: openshell.v1.SshRelayTarget + (*TcpRelayTarget)(nil), // 170: openshell.v1.TcpRelayTarget + (*RelayInit)(nil), // 171: openshell.v1.RelayInit + (*RelayFrame)(nil), // 172: openshell.v1.RelayFrame + (*RelayOpenResult)(nil), // 173: openshell.v1.RelayOpenResult + (*RelayClose)(nil), // 174: openshell.v1.RelayClose + (*L7RequestSample)(nil), // 175: openshell.v1.L7RequestSample + (*DenialSummary)(nil), // 176: openshell.v1.DenialSummary + (*DenialGroupCount)(nil), // 177: openshell.v1.DenialGroupCount + (*NetworkActivitySummary)(nil), // 178: openshell.v1.NetworkActivitySummary + (*PolicyChunk)(nil), // 179: openshell.v1.PolicyChunk + (*DraftPolicyUpdate)(nil), // 180: openshell.v1.DraftPolicyUpdate + (*SubmitPolicyAnalysisRequest)(nil), // 181: openshell.v1.SubmitPolicyAnalysisRequest + (*SubmitPolicyAnalysisResponse)(nil), // 182: openshell.v1.SubmitPolicyAnalysisResponse + (*GetDraftPolicyRequest)(nil), // 183: openshell.v1.GetDraftPolicyRequest + (*GetDraftPolicyResponse)(nil), // 184: openshell.v1.GetDraftPolicyResponse + (*ApproveDraftChunkRequest)(nil), // 185: openshell.v1.ApproveDraftChunkRequest + (*ApproveDraftChunkResponse)(nil), // 186: openshell.v1.ApproveDraftChunkResponse + (*RejectDraftChunkRequest)(nil), // 187: openshell.v1.RejectDraftChunkRequest + (*RejectDraftChunkResponse)(nil), // 188: openshell.v1.RejectDraftChunkResponse + (*DraftChunkApproval)(nil), // 189: openshell.v1.DraftChunkApproval + (*ApproveAllDraftChunksRequest)(nil), // 190: openshell.v1.ApproveAllDraftChunksRequest + (*ApproveAllDraftChunksResponse)(nil), // 191: openshell.v1.ApproveAllDraftChunksResponse + (*EditDraftChunkRequest)(nil), // 192: openshell.v1.EditDraftChunkRequest + (*EditDraftChunkResponse)(nil), // 193: openshell.v1.EditDraftChunkResponse + (*UndoDraftChunkRequest)(nil), // 194: openshell.v1.UndoDraftChunkRequest + (*UndoDraftChunkResponse)(nil), // 195: openshell.v1.UndoDraftChunkResponse + (*ClearDraftChunksRequest)(nil), // 196: openshell.v1.ClearDraftChunksRequest + (*ClearDraftChunksResponse)(nil), // 197: openshell.v1.ClearDraftChunksResponse + (*GetDraftHistoryRequest)(nil), // 198: openshell.v1.GetDraftHistoryRequest + (*DraftHistoryEntry)(nil), // 199: openshell.v1.DraftHistoryEntry + (*GetDraftHistoryResponse)(nil), // 200: openshell.v1.GetDraftHistoryResponse + (*CreateWorkspaceRequest)(nil), // 201: openshell.v1.CreateWorkspaceRequest + (*CreateWorkspaceResponse)(nil), // 202: openshell.v1.CreateWorkspaceResponse + (*GetWorkspaceRequest)(nil), // 203: openshell.v1.GetWorkspaceRequest + (*GetWorkspaceResponse)(nil), // 204: openshell.v1.GetWorkspaceResponse + (*ListWorkspacesRequest)(nil), // 205: openshell.v1.ListWorkspacesRequest + (*ListWorkspacesResponse)(nil), // 206: openshell.v1.ListWorkspacesResponse + (*DeleteWorkspaceRequest)(nil), // 207: openshell.v1.DeleteWorkspaceRequest + (*DeleteWorkspaceResponse)(nil), // 208: openshell.v1.DeleteWorkspaceResponse + (*WorkspaceMember)(nil), // 209: openshell.v1.WorkspaceMember + (*AddWorkspaceMemberRequest)(nil), // 210: openshell.v1.AddWorkspaceMemberRequest + (*AddWorkspaceMemberResponse)(nil), // 211: openshell.v1.AddWorkspaceMemberResponse + (*RemoveWorkspaceMemberRequest)(nil), // 212: openshell.v1.RemoveWorkspaceMemberRequest + (*RemoveWorkspaceMemberResponse)(nil), // 213: openshell.v1.RemoveWorkspaceMemberResponse + (*ListWorkspaceMembersRequest)(nil), // 214: openshell.v1.ListWorkspaceMembersRequest + (*ListWorkspaceMembersResponse)(nil), // 215: openshell.v1.ListWorkspaceMembersResponse + (*ExtensionServiceCredential)(nil), // 216: openshell.v1.ExtensionServiceCredential + (*EndpointObservation)(nil), // 217: openshell.v1.EndpointObservation + (*ReportEndpointStatusRequest)(nil), // 218: openshell.v1.ReportEndpointStatusRequest + (*ReportEndpointStatusResponse)(nil), // 219: openshell.v1.ReportEndpointStatusResponse + (*EndpointStatus)(nil), // 220: openshell.v1.EndpointStatus + nil, // 221: openshell.v1.SandboxSpec.EnvironmentEntry + nil, // 222: openshell.v1.SandboxTemplate.LabelsEntry + nil, // 223: openshell.v1.SandboxTemplate.AnnotationsEntry + nil, // 224: openshell.v1.SandboxTemplate.EnvironmentEntry + nil, // 225: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + nil, // 226: openshell.v1.PlatformEvent.MetadataEntry + nil, // 227: openshell.v1.CreateSandboxRequest.LabelsEntry + nil, // 228: openshell.v1.CreateSandboxRequest.AnnotationsEntry + nil, // 229: openshell.v1.ExecSandboxRequest.EnvironmentEntry + nil, // 230: openshell.v1.SandboxLogLine.FieldsEntry + nil, // 231: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + nil, // 232: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + nil, // 233: openshell.v1.ProviderProfile.AnnotationsEntry + nil, // 234: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + nil, // 235: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + nil, // 236: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + nil, // 237: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + nil, // 238: openshell.v1.UpdateConfigRequest.AnnotationsEntry + nil, // 239: openshell.v1.UpdateConfigResponse.AnnotationsEntry + nil, // 240: openshell.v1.SandboxPolicyRevision.ProvenanceEntry + nil, // 241: openshell.v1.CreateWorkspaceRequest.LabelsEntry + (*timestamppb.Timestamp)(nil), // 242: google.protobuf.Timestamp + (*datamodelv1.ObjectMeta)(nil), // 243: openshell.datamodel.v1.ObjectMeta + (*sandboxv1.SandboxPolicy)(nil), // 244: openshell.sandbox.v1.SandboxPolicy + (*structpb.Struct)(nil), // 245: google.protobuf.Struct + (*durationpb.Duration)(nil), // 246: google.protobuf.Duration + (*datamodelv1.WorkspaceSelector)(nil), // 247: openshell.datamodel.v1.WorkspaceSelector + (*datamodelv1.Provider)(nil), // 248: openshell.datamodel.v1.Provider + (*sandboxv1.NetworkEndpoint)(nil), // 249: openshell.sandbox.v1.NetworkEndpoint + (*sandboxv1.NetworkBinary)(nil), // 250: openshell.sandbox.v1.NetworkBinary + (*sandboxv1.SettingValue)(nil), // 251: openshell.sandbox.v1.SettingValue + (*sandboxv1.NetworkPolicyRule)(nil), // 252: openshell.sandbox.v1.NetworkPolicyRule + (*sandboxv1.L7DenyRule)(nil), // 253: openshell.sandbox.v1.L7DenyRule + (*sandboxv1.L7Rule)(nil), // 254: openshell.sandbox.v1.L7Rule + (*datamodelv1.Workspace)(nil), // 255: openshell.datamodel.v1.Workspace + (*sandboxv1.GetSandboxConfigRequest)(nil), // 256: openshell.sandbox.v1.GetSandboxConfigRequest + (*sandboxv1.GetGatewayConfigRequest)(nil), // 257: openshell.sandbox.v1.GetGatewayConfigRequest + (*sandboxv1.GetSandboxConfigResponse)(nil), // 258: openshell.sandbox.v1.GetSandboxConfigResponse + (*sandboxv1.GetGatewayConfigResponse)(nil), // 259: openshell.sandbox.v1.GetGatewayConfigResponse } var file_openshell_proto_depIdxs = []int32{ - 241, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp - 241, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp - 215, // 2: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential - 241, // 3: openshell.v1.RefreshSandboxTokenResponse.sandbox_expiration_time:type_name -> google.protobuf.Timestamp + 242, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp + 242, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp + 216, // 2: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential + 242, // 3: openshell.v1.RefreshSandboxTokenResponse.sandbox_expiration_time:type_name -> google.protobuf.Timestamp 5, // 4: openshell.v1.HealthResponse.status:type_name -> openshell.v1.ServiceStatus 5, // 5: openshell.v1.GetGatewayInfoResponse.status:type_name -> openshell.v1.ServiceStatus - 19, // 6: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo - 20, // 7: openshell.v1.ComputeDriverInfo.capabilities:type_name -> openshell.v1.ComputeDriverCapabilities - 21, // 8: openshell.v1.ComputeDriverCapabilities.resource_capabilities:type_name -> openshell.v1.ResourceCapabilities - 22, // 9: openshell.v1.ResourceCapabilities.cpu:type_name -> openshell.v1.CpuResourceCapabilities - 23, // 10: openshell.v1.ResourceCapabilities.memory:type_name -> openshell.v1.MemoryResourceCapabilities - 24, // 11: openshell.v1.ResourceCapabilities.gpu:type_name -> openshell.v1.GpuResourceCapabilities - 242, // 12: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 26, // 13: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec - 37, // 14: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus - 36, // 15: openshell.v1.Sandbox.created_from_workload_template:type_name -> openshell.v1.SandboxWorkloadTemplateProvenance - 220, // 16: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry - 29, // 17: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate - 243, // 18: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 27, // 19: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements - 28, // 20: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements - 221, // 21: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry - 222, // 22: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry - 223, // 23: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry - 244, // 24: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct - 244, // 25: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct - 242, // 26: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 31, // 27: openshell.v1.SandboxWorkloadTemplate.spec:type_name -> openshell.v1.SandboxWorkloadTemplateSpec - 32, // 28: openshell.v1.SandboxWorkloadTemplateSpec.workload:type_name -> openshell.v1.SandboxWorkloadConfig - 244, // 29: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct - 34, // 30: openshell.v1.SandboxWorkloadTemplateSpec.desired_service_level:type_name -> openshell.v1.SandboxServiceLevel - 224, // 31: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry - 33, // 32: openshell.v1.SandboxWorkloadConfig.resources:type_name -> openshell.v1.SandboxResources - 28, // 33: openshell.v1.SandboxResources.gpu:type_name -> openshell.v1.GpuResourceRequirements - 35, // 34: openshell.v1.SandboxServiceLevel.startup:type_name -> openshell.v1.SandboxStartup - 245, // 35: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration - 38, // 36: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition + 20, // 6: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo + 21, // 7: openshell.v1.ComputeDriverInfo.capabilities:type_name -> openshell.v1.ComputeDriverCapabilities + 22, // 8: openshell.v1.ComputeDriverCapabilities.resource_capabilities:type_name -> openshell.v1.ResourceCapabilities + 23, // 9: openshell.v1.ResourceCapabilities.cpu:type_name -> openshell.v1.CpuResourceCapabilities + 24, // 10: openshell.v1.ResourceCapabilities.memory:type_name -> openshell.v1.MemoryResourceCapabilities + 25, // 11: openshell.v1.ResourceCapabilities.gpu:type_name -> openshell.v1.GpuResourceCapabilities + 243, // 12: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 27, // 13: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec + 38, // 14: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus + 37, // 15: openshell.v1.Sandbox.created_from_workload_template:type_name -> openshell.v1.SandboxWorkloadTemplateProvenance + 221, // 16: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry + 30, // 17: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate + 244, // 18: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 28, // 19: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements + 29, // 20: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements + 222, // 21: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry + 223, // 22: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry + 224, // 23: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry + 245, // 24: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct + 245, // 25: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct + 243, // 26: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 32, // 27: openshell.v1.SandboxWorkloadTemplate.spec:type_name -> openshell.v1.SandboxWorkloadTemplateSpec + 33, // 28: openshell.v1.SandboxWorkloadTemplateSpec.workload:type_name -> openshell.v1.SandboxWorkloadConfig + 245, // 29: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct + 35, // 30: openshell.v1.SandboxWorkloadTemplateSpec.desired_service_level:type_name -> openshell.v1.SandboxServiceLevel + 225, // 31: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + 34, // 32: openshell.v1.SandboxWorkloadConfig.resources:type_name -> openshell.v1.SandboxResources + 29, // 33: openshell.v1.SandboxResources.gpu:type_name -> openshell.v1.GpuResourceRequirements + 36, // 34: openshell.v1.SandboxServiceLevel.startup:type_name -> openshell.v1.SandboxStartup + 246, // 35: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration + 39, // 36: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition 0, // 37: openshell.v1.SandboxStatus.phase:type_name -> openshell.v1.SandboxPhase - 219, // 38: openshell.v1.SandboxStatus.endpoint_statuses:type_name -> openshell.v1.EndpointStatus - 241, // 39: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp - 241, // 40: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp - 225, // 41: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry - 26, // 42: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec - 226, // 43: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry - 227, // 44: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry - 246, // 45: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 30, // 46: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate - 246, // 47: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 48: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 49: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 50: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 30, // 51: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate - 30, // 52: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate - 246, // 53: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 241, // 54: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp - 246, // 55: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 56: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 57: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 58: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 59: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 60: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 61: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 62: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 25, // 63: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox - 25, // 64: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox - 247, // 65: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 25, // 66: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 25, // 67: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 241, // 68: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp - 246, // 69: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 70: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 71: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 73, // 72: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse - 246, // 73: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 242, // 74: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 72, // 75: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint - 228, // 76: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry - 245, // 77: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration - 77, // 78: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout - 78, // 79: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr - 79, // 80: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit - 168, // 81: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget - 169, // 82: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget - 81, // 83: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit - 76, // 84: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest - 84, // 85: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize - 242, // 86: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 241, // 87: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp - 241, // 88: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp - 25, // 89: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox - 88, // 90: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine - 39, // 91: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent - 89, // 92: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning - 179, // 93: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate - 241, // 94: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp - 229, // 95: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry - 247, // 96: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 246, // 97: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 98: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 99: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 247, // 100: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 230, // 101: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - 246, // 102: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 103: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 247, // 104: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider - 247, // 105: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 118, // 106: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile - 245, // 107: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration - 101, // 108: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride - 1, // 109: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType - 102, // 110: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken - 107, // 111: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh - 103, // 112: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant - 2, // 113: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 245, // 114: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration - 245, // 115: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration - 105, // 116: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial - 106, // 117: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput - 2, // 118: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 241, // 119: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp - 241, // 120: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp - 241, // 121: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp - 7, // 122: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 241, // 123: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp - 246, // 124: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 108, // 125: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 2, // 126: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 231, // 127: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - 241, // 128: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp - 246, // 129: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 108, // 130: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 246, // 131: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 108, // 132: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 246, // 133: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 3, // 134: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory - 104, // 135: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential - 248, // 136: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint - 249, // 137: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 109, // 138: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery - 232, // 139: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry - 118, // 140: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile - 118, // 141: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 99, // 142: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 100, // 143: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 118, // 144: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 99, // 145: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem - 100, // 146: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 118, // 147: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile - 99, // 148: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 100, // 149: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 131, // 150: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding - 233, // 151: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - 234, // 152: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - 235, // 153: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - 236, // 154: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - 245, // 155: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration - 243, // 156: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 250, // 157: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue - 137, // 158: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation - 237, // 159: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry - 246, // 160: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 138, // 161: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule - 139, // 162: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint - 140, // 163: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule - 141, // 164: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules - 142, // 165: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules - 143, // 166: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary - 251, // 167: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 252, // 168: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule - 253, // 169: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule - 238, // 170: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry - 246, // 171: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 151, // 172: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision - 246, // 173: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 151, // 174: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision - 4, // 175: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus - 4, // 176: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus - 241, // 177: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp - 241, // 178: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp - 243, // 179: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 239, // 180: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry - 241, // 181: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp - 246, // 182: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 88, // 183: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine - 88, // 184: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine - 158, // 185: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello - 161, // 186: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat - 172, // 187: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult - 173, // 188: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose - 159, // 189: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted - 160, // 190: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected - 162, // 191: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat - 167, // 192: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen - 173, // 193: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose - 245, // 194: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration - 168, // 195: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget - 169, // 196: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget - 170, // 197: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit - 241, // 198: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp - 241, // 199: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp - 174, // 200: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample - 176, // 201: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount - 251, // 202: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 241, // 203: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp - 241, // 204: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp - 241, // 205: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp - 241, // 206: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp - 243, // 207: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 243, // 208: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 175, // 209: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary - 178, // 210: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk - 177, // 211: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary - 246, // 212: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 178, // 213: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk - 241, // 214: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp - 246, // 215: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 216: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 188, // 217: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval - 246, // 218: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 251, // 219: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 246, // 220: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 221: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 222: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 246, // 223: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 241, // 224: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp - 198, // 225: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry - 240, // 226: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry - 254, // 227: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 254, // 228: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 254, // 229: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace - 242, // 230: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 6, // 231: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole - 6, // 232: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole - 208, // 233: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember - 208, // 234: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember - 241, // 235: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp - 8, // 236: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult - 216, // 237: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation - 8, // 238: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult - 241, // 239: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp - 241, // 240: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 241, // 241: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 104, // 242: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 132, // 243: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding - 13, // 244: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest - 15, // 245: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest - 17, // 246: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest - 40, // 247: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest - 48, // 248: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest - 50, // 249: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest - 51, // 250: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest - 41, // 251: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest - 42, // 252: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest - 43, // 253: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest - 44, // 254: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest - 52, // 255: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest - 53, // 256: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest - 54, // 257: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest - 55, // 258: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest - 56, // 259: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest - 57, // 260: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest - 64, // 261: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest - 66, // 262: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest - 67, // 263: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest - 68, // 264: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest - 70, // 265: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest - 74, // 266: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest - 76, // 267: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest - 82, // 268: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame - 83, // 269: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput - 90, // 270: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest - 91, // 271: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest - 92, // 272: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest - 97, // 273: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest - 98, // 274: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest - 121, // 275: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest - 123, // 276: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest - 125, // 277: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest - 93, // 278: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest - 110, // 279: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest - 112, // 280: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest - 114, // 281: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest - 116, // 282: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest - 94, // 283: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest - 128, // 284: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest - 255, // 285: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest - 256, // 286: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest - 136, // 287: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest - 145, // 288: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest - 147, // 289: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest - 149, // 290: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest - 217, // 291: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest - 130, // 292: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest - 134, // 293: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest - 152, // 294: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest - 153, // 295: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest - 156, // 296: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage - 163, // 297: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest - 165, // 298: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest - 171, // 299: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame - 86, // 300: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest - 180, // 301: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest - 182, // 302: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest - 184, // 303: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest - 186, // 304: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest - 189, // 305: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest - 191, // 306: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest - 193, // 307: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest - 195, // 308: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest - 197, // 309: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest - 9, // 310: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest - 11, // 311: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest - 200, // 312: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest - 202, // 313: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest - 204, // 314: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest - 206, // 315: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest - 209, // 316: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest - 211, // 317: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest - 213, // 318: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest - 14, // 319: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse - 16, // 320: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse - 18, // 321: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse - 58, // 322: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse - 49, // 323: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse - 58, // 324: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse - 59, // 325: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse - 45, // 326: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 45, // 327: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 46, // 328: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse - 47, // 329: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse - 60, // 330: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse - 61, // 331: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse - 62, // 332: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse - 63, // 333: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse - 58, // 334: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse - 58, // 335: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse - 65, // 336: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse - 73, // 337: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse - 73, // 338: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse - 69, // 339: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse - 71, // 340: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse - 75, // 341: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse - 80, // 342: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent - 82, // 343: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame - 80, // 344: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent - 95, // 345: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse - 95, // 346: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse - 96, // 347: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse - 120, // 348: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse - 119, // 349: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse - 122, // 350: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse - 124, // 351: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse - 126, // 352: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse - 95, // 353: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse - 111, // 354: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse - 113, // 355: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse - 115, // 356: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse - 117, // 357: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse - 127, // 358: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse - 129, // 359: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse - 257, // 360: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse - 258, // 361: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse - 144, // 362: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse - 146, // 363: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse - 148, // 364: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse - 150, // 365: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse - 218, // 366: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse - 133, // 367: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse - 135, // 368: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse - 155, // 369: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse - 154, // 370: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse - 157, // 371: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage - 164, // 372: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse - 166, // 373: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse - 171, // 374: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame - 87, // 375: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent - 181, // 376: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse - 183, // 377: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse - 185, // 378: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse - 187, // 379: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse - 190, // 380: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse - 192, // 381: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse - 194, // 382: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse - 196, // 383: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse - 199, // 384: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse - 10, // 385: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse - 12, // 386: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse - 201, // 387: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse - 203, // 388: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse - 205, // 389: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse - 207, // 390: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse - 210, // 391: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse - 212, // 392: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse - 214, // 393: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse - 319, // [319:394] is the sub-list for method output_type - 244, // [244:319] is the sub-list for method input_type - 244, // [244:244] is the sub-list for extension type_name - 244, // [244:244] is the sub-list for extension extendee - 0, // [0:244] is the sub-list for field type_name + 220, // 38: openshell.v1.SandboxStatus.endpoint_statuses:type_name -> openshell.v1.EndpointStatus + 242, // 39: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp + 242, // 40: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp + 226, // 41: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry + 27, // 42: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec + 227, // 43: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry + 228, // 44: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry + 247, // 45: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 31, // 46: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate + 247, // 47: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 48: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 49: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 50: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 31, // 51: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate + 31, // 52: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate + 8, // 53: openshell.v1.DeleteSandboxTemplateResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 247, // 54: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 242, // 55: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp + 247, // 56: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 57: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 58: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 59: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 60: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 61: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 62: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 63: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 26, // 64: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox + 26, // 65: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox + 248, // 66: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 26, // 67: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 26, // 68: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 8, // 69: openshell.v1.DeleteSandboxResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 242, // 70: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp + 247, // 71: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 72: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 73: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 74, // 74: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse + 247, // 75: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 8, // 76: openshell.v1.DeleteServiceResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 243, // 77: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 73, // 78: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint + 8, // 79: openshell.v1.RevokeSshSessionResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 229, // 80: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry + 246, // 81: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration + 78, // 82: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout + 79, // 83: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr + 80, // 84: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit + 169, // 85: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget + 170, // 86: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget + 82, // 87: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit + 77, // 88: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest + 85, // 89: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize + 243, // 90: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 242, // 91: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp + 242, // 92: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp + 26, // 93: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox + 89, // 94: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine + 40, // 95: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent + 90, // 96: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning + 180, // 97: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate + 242, // 98: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp + 230, // 99: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry + 248, // 100: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 247, // 101: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 102: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 103: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 248, // 104: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 231, // 105: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + 247, // 106: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 107: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 248, // 108: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider + 248, // 109: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 119, // 110: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile + 246, // 111: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration + 102, // 112: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride + 1, // 113: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType + 103, // 114: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken + 108, // 115: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh + 104, // 116: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant + 2, // 117: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 246, // 118: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration + 246, // 119: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration + 106, // 120: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial + 107, // 121: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput + 2, // 122: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 242, // 123: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp + 242, // 124: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp + 242, // 125: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp + 7, // 126: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction + 242, // 127: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp + 247, // 128: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 109, // 129: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 2, // 130: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 232, // 131: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + 242, // 132: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp + 247, // 133: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 109, // 134: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 247, // 135: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 109, // 136: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 247, // 137: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 8, // 138: openshell.v1.DeleteProviderRefreshResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 3, // 139: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory + 105, // 140: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential + 249, // 141: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 250, // 142: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 110, // 143: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery + 233, // 144: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry + 119, // 145: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile + 119, // 146: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 100, // 147: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 101, // 148: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 119, // 149: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 100, // 150: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem + 101, // 151: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 119, // 152: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile + 100, // 153: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 101, // 154: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 8, // 155: openshell.v1.DeleteProviderResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 8, // 156: openshell.v1.DeleteProviderProfileResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 132, // 157: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding + 234, // 158: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + 235, // 159: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + 236, // 160: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + 237, // 161: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + 246, // 162: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration + 244, // 163: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 251, // 164: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue + 138, // 165: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation + 238, // 166: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry + 247, // 167: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 139, // 168: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule + 140, // 169: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint + 141, // 170: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule + 142, // 171: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules + 143, // 172: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules + 144, // 173: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary + 252, // 174: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 253, // 175: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 254, // 176: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule + 239, // 177: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry + 247, // 178: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 152, // 179: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision + 247, // 180: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 152, // 181: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision + 4, // 182: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus + 4, // 183: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus + 242, // 184: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp + 242, // 185: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp + 244, // 186: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 240, // 187: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry + 242, // 188: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp + 247, // 189: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 89, // 190: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine + 89, // 191: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine + 159, // 192: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello + 162, // 193: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat + 173, // 194: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult + 174, // 195: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose + 160, // 196: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted + 161, // 197: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected + 163, // 198: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat + 168, // 199: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen + 174, // 200: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose + 246, // 201: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration + 169, // 202: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget + 170, // 203: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget + 171, // 204: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit + 242, // 205: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp + 242, // 206: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp + 175, // 207: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample + 177, // 208: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount + 252, // 209: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 242, // 210: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp + 242, // 211: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp + 242, // 212: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp + 242, // 213: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp + 244, // 214: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 244, // 215: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 176, // 216: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary + 179, // 217: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk + 178, // 218: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary + 247, // 219: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 179, // 220: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk + 242, // 221: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp + 247, // 222: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 223: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 189, // 224: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval + 247, // 225: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 252, // 226: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 247, // 227: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 228: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 229: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 247, // 230: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 242, // 231: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp + 199, // 232: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry + 241, // 233: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry + 255, // 234: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 255, // 235: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 255, // 236: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace + 8, // 237: openshell.v1.DeleteWorkspaceResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 243, // 238: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 6, // 239: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole + 6, // 240: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole + 209, // 241: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember + 8, // 242: openshell.v1.RemoveWorkspaceMemberResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 209, // 243: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember + 242, // 244: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp + 9, // 245: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult + 217, // 246: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation + 9, // 247: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult + 242, // 248: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp + 242, // 249: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 242, // 250: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 105, // 251: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 133, // 252: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding + 14, // 253: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest + 16, // 254: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest + 18, // 255: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest + 41, // 256: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest + 49, // 257: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest + 51, // 258: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest + 52, // 259: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest + 42, // 260: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest + 43, // 261: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest + 44, // 262: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest + 45, // 263: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest + 53, // 264: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest + 54, // 265: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest + 55, // 266: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest + 56, // 267: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest + 57, // 268: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest + 58, // 269: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest + 65, // 270: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest + 67, // 271: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest + 68, // 272: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest + 69, // 273: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest + 71, // 274: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest + 75, // 275: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest + 77, // 276: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest + 83, // 277: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame + 84, // 278: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput + 91, // 279: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest + 92, // 280: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest + 93, // 281: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest + 98, // 282: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest + 99, // 283: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest + 122, // 284: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest + 124, // 285: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest + 126, // 286: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest + 94, // 287: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest + 111, // 288: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest + 113, // 289: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest + 115, // 290: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest + 117, // 291: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest + 95, // 292: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest + 129, // 293: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest + 256, // 294: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest + 257, // 295: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest + 137, // 296: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest + 146, // 297: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest + 148, // 298: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest + 150, // 299: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest + 218, // 300: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest + 131, // 301: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest + 135, // 302: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest + 153, // 303: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest + 154, // 304: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest + 157, // 305: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage + 164, // 306: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest + 166, // 307: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest + 172, // 308: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame + 87, // 309: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest + 181, // 310: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest + 183, // 311: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest + 185, // 312: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest + 187, // 313: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest + 190, // 314: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest + 192, // 315: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest + 194, // 316: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest + 196, // 317: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest + 198, // 318: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest + 10, // 319: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest + 12, // 320: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest + 201, // 321: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest + 203, // 322: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest + 205, // 323: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest + 207, // 324: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest + 210, // 325: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest + 212, // 326: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest + 214, // 327: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest + 15, // 328: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse + 17, // 329: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse + 19, // 330: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse + 59, // 331: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse + 50, // 332: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse + 59, // 333: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse + 60, // 334: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse + 46, // 335: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 46, // 336: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 47, // 337: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse + 48, // 338: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse + 61, // 339: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse + 62, // 340: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse + 63, // 341: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse + 64, // 342: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse + 59, // 343: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse + 59, // 344: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse + 66, // 345: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse + 74, // 346: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse + 74, // 347: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse + 70, // 348: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse + 72, // 349: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse + 76, // 350: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse + 81, // 351: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent + 83, // 352: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame + 81, // 353: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent + 96, // 354: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse + 96, // 355: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse + 97, // 356: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse + 121, // 357: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse + 120, // 358: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse + 123, // 359: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse + 125, // 360: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse + 127, // 361: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse + 96, // 362: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse + 112, // 363: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse + 114, // 364: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse + 116, // 365: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse + 118, // 366: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse + 128, // 367: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse + 130, // 368: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse + 258, // 369: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse + 259, // 370: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse + 145, // 371: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse + 147, // 372: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse + 149, // 373: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse + 151, // 374: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse + 219, // 375: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse + 134, // 376: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse + 136, // 377: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse + 156, // 378: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse + 155, // 379: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse + 158, // 380: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage + 165, // 381: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse + 167, // 382: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse + 172, // 383: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame + 88, // 384: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent + 182, // 385: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse + 184, // 386: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse + 186, // 387: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse + 188, // 388: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse + 191, // 389: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse + 193, // 390: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse + 195, // 391: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse + 197, // 392: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse + 200, // 393: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse + 11, // 394: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse + 13, // 395: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse + 202, // 396: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse + 204, // 397: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse + 206, // 398: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse + 208, // 399: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse + 211, // 400: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse + 213, // 401: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse + 215, // 402: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse + 328, // [328:403] is the sub-list for method output_type + 253, // [253:328] is the sub-list for method input_type + 253, // [253:253] is the sub-list for extension type_name + 253, // [253:253] is the sub-list for extension extendee + 0, // [0:253] is the sub-list for field type_name } func init() { file_openshell_proto_init() } @@ -16760,7 +16935,7 @@ func file_openshell_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_openshell_proto_rawDesc), len(file_openshell_proto_rawDesc)), - NumEnums: 9, + NumEnums: 10, NumMessages: 232, NumExtensions: 0, NumServices: 1, diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index 23d5d8f84b..52122ccfb6 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -41,9 +41,27 @@ await client.sandbox.waitReady(sandbox.name, 120) const result = await client.sandbox.exec(sandbox.name, ['/bin/sh', '-c', 'echo hello']) console.log(result.stdout.toString()) -await client.sandbox.delete(sandbox.name) +const deletion = await client.sandbox.delete(sandbox.name) +console.log(deletion.outcome) // completed, accepted, or already_absent +if (deletion.outcome === 'accepted') { + await client.sandbox.waitDeleted(sandbox.name, 60, { + expectedSandboxId: deletion.sandboxId, + }) +} ``` +Deletion returns a typed result, not a boolean. `accepted` means cleanup is +pending; `unspecified` and unknown outcomes do not establish completion. +`sandboxId` identifies the original sandbox. Missing targets are errors unless +you pass `{ allowMissing: true }`; this does not suppress missing parents or make +retries safe when names are reused. Upgrade gateway and SDK together for this +pre-1.0 API change. + +`waitDeleted` with `expectedSandboxId` completes when the name is absent or +resolves to a different ID. Without that option, it waits for name absence, +including any same-name replacement. Pass the same `workspace` to deletion and +its wait when using a non-default workspace. + `connect()` constructs a lazy client; call `health()` when startup must verify gateway reachability. Static `oidcToken` and `edgeToken` values remain fixed for the client's lifetime. For long-running service automation, use the renewable diff --git a/sdk/typescript/src/client.test.ts b/sdk/typescript/src/client.test.ts index 48330d0bdb..efdd6aabcc 100644 --- a/sdk/typescript/src/client.test.ts +++ b/sdk/typescript/src/client.test.ts @@ -57,6 +57,27 @@ function readySandbox( const enc = (s: string) => new TextEncoder().encode(s); +describe('deletion outcomes', () => { + it('defaults to strict deletion and preserves accepted identity and unknown values', async () => { + const flags: boolean[] = []; + let outcome = 2; + const sandbox = client({ + deleteSandbox: (req) => { + flags.push(req.allowMissing); + return { outcome, sandboxId: 'original-id' }; + }, + }); + expect(await sandbox.delete('sandbox')).toEqual({ outcome: 'accepted', rawOutcome: 2, sandboxId: 'original-id' }); + outcome = 99; + expect(await sandbox.delete('sandbox', { allowMissing: true })).toEqual({ + outcome: 'unknown', + rawOutcome: 99, + sandboxId: 'original-id', + }); + expect(flags).toEqual([false, true]); + }); +}); + type ScopedRequest = { workspaceScope?: { selection?: { case?: string; value?: unknown } }; }; @@ -400,7 +421,7 @@ describe('create', () => { }, deleteSandbox: (req) => { observed.delete = req; - return { deleted: true }; + return { outcome: 1 }; }, attachSandboxProvider: (req) => { observed.attach = req; @@ -454,7 +475,7 @@ describe('create', () => { hostKeyFingerprint: '', expiresAtMs: 0n, }), - revokeSshSession: () => ({ revoked: true }), + revokeSshSession: () => ({ outcome: 1 }), }); const created = await sandbox.create({ name: 'direct', workspace: 'staging', image: 'img' }); @@ -485,7 +506,7 @@ describe('create', () => { expect(created.workspace).toBe('staging'); expect(got.workspace).toBe('staging'); expect(listed[0]?.workspace).toBe('staging'); - expect(deleted).toBe(true); + expect(deleted.outcome).toBe('completed'); expect(attached.sandbox.workspace).toBe('staging'); expect(detached.sandbox.workspace).toBe('staging'); expect(selectedWorkspace(observed.create ?? {})).toBe('staging'); @@ -688,7 +709,7 @@ describe('sandbox templates', () => { }, deleteSandboxTemplate: (req) => { observed.delete = req; - return { deleted: true }; + return { outcome: 1 }; }, }); @@ -698,7 +719,7 @@ describe('sandbox templates', () => { expect(got.metadata?.name).toBe('gpu-kata'); expect(listed).toHaveLength(1); - expect(deleted).toBe(true); + expect(deleted.outcome).toBe('completed'); expect(observed.get).toMatchObject({ name: 'gpu-kata' }); expect(selectedWorkspace(observed.get ?? {})).toBe('staging'); expect(observed.list).toMatchObject({ @@ -788,13 +809,82 @@ describe('waits', () => { }); }); - it('waitDeleted resolves when the gateway reports NotFound', async () => { + it.each([undefined, 'old-id'])('waitDeleted resolves on NotFound with expected ID %s', async (expectedSandboxId) => { const sandbox = client({ getSandbox: () => { throw new ConnectError('gone', Code.NotFound); }, }); - await expect(sandbox.waitDeleted('sb', 1)).resolves.toBeUndefined(); + await expect(sandbox.waitDeleted('sb', 1, { expectedSandboxId })).resolves.toBeUndefined(); + }); + + it.each([undefined, 'team'])( + 'waitDeleted completes on replacement after accepted deletion in %s', + async (workspace) => { + let polls = 0; + const sandbox = client({ + deleteSandbox: (req) => { + expect(selectedWorkspace(req)).toBe(workspace ?? 'default'); + return { outcome: 2, sandboxId: 'old-id' }; + }, + getSandbox: (req) => { + expect(selectedWorkspace(req)).toBe(workspace ?? 'default'); + polls++; + return readySandbox('sb', 'replacement-id'); + }, + }); + const deletion = await sandbox.delete('sb', { workspace }); + expect(deletion.outcome).toBe('accepted'); + expect(deletion.sandboxId).toBe('old-id'); + await expect( + sandbox.waitDeleted('sb', 1, { + workspace, + expectedSandboxId: deletion.sandboxId, + }), + ).resolves.toBeUndefined(); + expect(polls).toBe(1); + }, + ); + + it('waitDeleted keeps polling the original identity until it disappears', async () => { + let polls = 0; + const sandbox = client({ + getSandbox: () => { + if (++polls === 1) return readySandbox('sb', 'old-id'); + throw new ConnectError('gone', Code.NotFound); + }, + }); + await expect(sandbox.waitDeleted('sb', 5, { expectedSandboxId: 'old-id' })).resolves.toBeUndefined(); + expect(polls).toBe(2); + }); + + it.each([undefined, 'replacement-id'])( + 'waitDeleted times out while observed identity remains with expected ID %s', + async (expectedSandboxId) => { + let polls = 0; + const sandbox = client({ + getSandbox: () => { + polls++; + return readySandbox('sb', 'replacement-id'); + }, + }); + await expect(sandbox.waitDeleted('sb', 0.2, { expectedSandboxId })).rejects.toMatchObject({ + code: 'connect', + message: "[connect] timed out waiting for sandbox 'sb' to delete", + }); + expect(polls).toBeGreaterThan(0); + }, + ); + + it.each([Code.PermissionDenied, Code.Unavailable])('waitDeleted propagates lookup error %s', async (code) => { + const sandbox = client({ + getSandbox: () => { + throw new ConnectError('lookup failed', code); + }, + }); + await expect(sandbox.waitDeleted('sb', 1, { expectedSandboxId: 'old-id' })).rejects.toMatchObject({ + connectCode: code, + }); }); it('waitDeleted rejects rather than hanging when get() never resolves', async () => { @@ -1181,8 +1271,8 @@ describe('ssh sessions', () => { }); it('revokeSshSession returns the revoked flag', async () => { - const sandbox = client({ revokeSshSession: () => ({ revoked: true }) }); - expect(await sandbox.revokeSshSession('tok')).toBe(true); + const sandbox = client({ revokeSshSession: () => ({ outcome: 1 }) }); + expect((await sandbox.revokeSshSession('tok')).outcome).toBe('completed'); }); it('rejects a response that violates the ProxyCommand trust-boundary contract', async () => { @@ -1256,7 +1346,7 @@ describe('forward', () => { }, revokeSshSession: (req) => { revokedToken = req.token; - return { revoked: true }; + return { outcome: 1 }; }, forwardTcp: async function* (requests) { for await (const frame of requests) { @@ -1336,7 +1426,7 @@ describe('forward', () => { hostKeyFingerprint: '', expirationTime: undefined, }), - revokeSshSession: () => ({ revoked: true }), + revokeSshSession: () => ({ outcome: 1 }), // Ignore inbound frames; just blast a large, verifiable byte stream back. forwardTcp: async function* () { for (let i = 0; i < CHUNKS; i++) { @@ -1402,7 +1492,7 @@ describe('forward', () => { forwardTcp: async function* () { return; }, - revokeSshSession: () => ({ revoked: true }), + revokeSshSession: () => ({ outcome: 1 }), }); const handle = await sandbox.forward('sb', { targetPort: 9000 }); @@ -1481,7 +1571,7 @@ describe('forward', () => { }); throw new ConnectError('canceled', Code.Canceled); }, - revokeSshSession: () => ({ revoked: true }), + revokeSshSession: () => ({ outcome: 1 }), }); const handle = await sandbox.forward('sb', { targetPort: 9000 }); diff --git a/sdk/typescript/src/client.ts b/sdk/typescript/src/client.ts index 144a01e642..b90ae6026b 100644 --- a/sdk/typescript/src/client.ts +++ b/sdk/typescript/src/client.ts @@ -98,6 +98,34 @@ export interface Health { version: string; } +export type DeletionOutcome = 'unspecified' | 'completed' | 'accepted' | 'already_absent' | 'unknown'; + +export interface DeletionResult { + outcome: DeletionOutcome; + /** Original enum number, including values introduced by a newer gateway. */ + rawOutcome: number; + /** Original sandbox UUID; absent if no target existed or this is not a sandbox deletion. */ + sandboxId?: string; +} + +export interface DeleteOptions extends SandboxWorkspaceOptions { + allowMissing?: boolean; +} + +function deletionResult(response: { outcome: number; sandboxId?: string }): DeletionResult { + const names: Record = { + 0: 'unspecified', + 1: 'completed', + 2: 'accepted', + 3: 'already_absent', + }; + return { + outcome: names[response.outcome] ?? 'unknown', + rawOutcome: response.outcome, + ...(response.sandboxId ? { sandboxId: response.sandboxId } : {}), + }; +} + export interface SandboxSpec { name?: string; /** Workspace name. Omit for `default`; empty strings are invalid. */ @@ -274,6 +302,11 @@ export interface WaitOptions extends SandboxWorkspaceOptions { signal?: AbortSignal; } +export interface WaitDeletedOptions extends WaitOptions { + /** Original ID from delete(). Complete on absence or a different ID; omit to wait for name absence. */ + expectedSandboxId?: string; +} + export interface ForwardOptions extends SandboxWorkspaceOptions { /** Loopback TCP port inside the sandbox to dial. */ targetPort: number; @@ -765,14 +798,15 @@ export class SandboxTemplateClient { return this.list(options).all(); } - async delete(name: string, options?: SandboxTemplateWorkspaceOptions | null): Promise { + async delete(name: string, options?: DeleteOptions | null): Promise { if (name.trim() === '') throw new SdkError('invalid_config', 'template name is required'); try { const resp = await this.grpc.deleteSandboxTemplate({ + allowMissing: options?.allowMissing ?? false, name, workspaceScope: workspaceScope(options), }); - return resp.deleted; + return deletionResult(resp); } catch (e) { throw fromConnect(e); } @@ -899,10 +933,14 @@ export class SandboxClient { return this.list(options).all(); } - async delete(name: string, options?: SandboxWorkspaceOptions | null): Promise { + async delete(name: string, options?: DeleteOptions | null): Promise { try { - const resp = await this.grpc.deleteSandbox({ name, workspaceScope: workspaceScope(options) }); - return resp.deleted; + const resp = await this.grpc.deleteSandbox({ + name, + workspaceScope: workspaceScope(options), + allowMissing: options?.allowMissing ?? false, + }); + return deletionResult(resp); } catch (e) { throw fromConnect(e); } @@ -937,9 +975,9 @@ export class SandboxClient { } } - // Poll until the sandbox is gone. Timeout and cancellation bound the returned - // promise the same way as waitReady. - async waitDeleted(name: string, timeoutSecs: number, options?: WaitOptions | null): Promise { + // Poll until the sandbox is gone, or its name resolves to a different ID when + // expectedSandboxId is supplied. Timeout and cancellation work as in waitReady. + async waitDeleted(name: string, timeoutSecs: number, options?: WaitDeletedOptions | null): Promise { const deadline = Date.now() + timeoutSecs * 1000; const signal = options?.signal; let delay = 250; @@ -948,7 +986,8 @@ export class SandboxClient { if (Date.now() >= deadline) throw new SdkError('connect', `timed out waiting for sandbox '${name}' to delete`); const pollOptions = deadlineOptions(deadline - Date.now(), signal); try { - await this.get(name, { ...pollOptions, workspace: options?.workspace }); + const ref = await this.get(name, { ...pollOptions, workspace: options?.workspace }); + if (options?.expectedSandboxId !== undefined && ref.id !== options.expectedSandboxId) return; } catch (e) { if (e instanceof SdkError && e.code === 'not_found') return; throw mapWaitError(e, name, deadline, signal, pollOptions.signal); @@ -1340,7 +1379,7 @@ export class SandboxClient { input.end(); if (token !== undefined) { try { - await this.grpc.revokeSshSession({ token }, { signal }); + await this.grpc.revokeSshSession({ token, allowMissing: true }, { signal }); } catch { // Best-effort revoke; the token expires on its own regardless. } @@ -1371,10 +1410,10 @@ export class SandboxClient { } } - async revokeSshSession(token: string): Promise { + async revokeSshSession(token: string, options?: Pick): Promise { try { - const resp = await this.grpc.revokeSshSession({ token }); - return resp.revoked; + const resp = await this.grpc.revokeSshSession({ token, allowMissing: options?.allowMissing ?? false }); + return deletionResult(resp); } catch (e) { throw fromConnect(e); } diff --git a/sdk/typescript/src/index.ts b/sdk/typescript/src/index.ts index 90d9c07705..d858cd75b6 100644 --- a/sdk/typescript/src/index.ts +++ b/sdk/typescript/src/index.ts @@ -5,6 +5,9 @@ // export type { ConnectOptions, + DeleteOptions, + DeletionOutcome, + DeletionResult, EffectiveSettingView, ExecExitEvent, ExecInteractiveOptions, @@ -43,6 +46,7 @@ export type { SettingValue, SshSession, UpdateConfigResult, + WaitDeletedOptions, WaitOptions, WorkspaceListScope, } from './client.js'; diff --git a/skills/openshell-cli/SKILL.md b/skills/openshell-cli/SKILL.md index 0d30aacb50..2e4c9c3bdf 100644 --- a/skills/openshell-cli/SKILL.md +++ b/skills/openshell-cli/SKILL.md @@ -410,6 +410,11 @@ openshell sandbox delete sandbox-1 sandbox-2 sandbox-3 # Multiple at once openshell sandbox delete --all ``` +`deletion accepted` means cleanup is still pending. Inspect the sandbox until +it disappears before assuming completion. An already-absent sandbox succeeds; +missing workspaces and authorization failures remain errors. Do not blindly +retry by name if another process might have recreated that name. + ### Stop and start sandboxes Use stop to halt compute while retaining the sandbox and its persistent