From 72f349d27144c16a2610ae935794a7fdcab91cbb Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 10 Sep 2026 21:17:27 -0700 Subject: [PATCH] feat(api)!: unify sandbox references Signed-off-by: Drew Newberry --- architecture/gateway.md | 17 +- crates/openshell-cli/src/commands/provider.rs | 25 +- crates/openshell-cli/src/main.rs | 15 +- crates/openshell-cli/src/run.rs | 249 ++- crates/openshell-cli/src/ssh.rs | 12 +- .../tests/provider_commands_integration.rs | 31 +- .../sandbox_create_lifecycle_integration.rs | 25 +- .../sandbox_name_fallback_integration.rs | 25 +- crates/openshell-core/src/grpc_client.rs | 14 +- crates/openshell-core/src/proto/mod.rs | 19 + .../src/proto_json.rs | 5 +- .../src/runtime.rs | 62 +- crates/openshell-sdk/src/client.rs | 28 +- crates/openshell-sdk/tests/client_mock.rs | 56 +- crates/openshell-server/src/grpc/auth_rpc.rs | 4 +- crates/openshell-server/src/grpc/policy.rs | 1207 +++++++------ crates/openshell-server/src/grpc/provider.rs | 8 +- crates/openshell-server/src/grpc/sandbox.rs | 593 ++++--- crates/openshell-server/src/grpc/service.rs | 351 ++-- .../openshell-server/src/grpc/validation.rs | 27 +- .../src/debug_rpc.rs | 2 +- crates/openshell-tui/src/lib.rs | 96 +- docs/sandboxes/manage-workspaces.mdx | 19 +- examples/governance-interceptor/src/main.rs | 6 +- .../src/smoke_client.rs | 14 +- proto/datamodel.proto | 15 + proto/openshell.proto | 238 +-- proto/sandbox.proto | 8 +- python/openshell/sandbox.py | 22 +- python/openshell/sandbox_test.py | 25 +- sdk/go/openshell/v1/config_client.go | 4 +- sdk/go/openshell/v1/config_client_test.go | 8 +- sdk/go/openshell/v1/doc.go | 4 +- sdk/go/openshell/v1/exec_client_test.go | 10 +- sdk/go/openshell/v1/fake/policy.go | 14 +- sdk/go/openshell/v1/fake/policy_test.go | 18 +- sdk/go/openshell/v1/file_client.go | 4 +- sdk/go/openshell/v1/file_client_test.go | 8 +- .../openshell/v1/internal/converter/exec.go | 7 +- .../v1/internal/converter/exec_test.go | 6 +- .../v1/internal/converter/setting.go | 7 +- .../v1/internal/converter/setting_test.go | 6 +- sdk/go/openshell/v1/policy.go | 2 +- sdk/go/openshell/v1/policy_client.go | 47 +- sdk/go/openshell/v1/policy_client_test.go | 54 +- sdk/go/openshell/v1/sandbox_client.go | 32 +- sdk/go/openshell/v1/sandbox_client_test.go | 50 +- sdk/go/openshell/v1/service_client.go | 26 +- sdk/go/openshell/v1/service_client_test.go | 21 +- sdk/go/openshell/v1/ssh_client.go | 4 +- sdk/go/openshell/v1/ssh_client_test.go | 9 +- sdk/go/openshell/v1/tcp_client.go | 4 +- sdk/go/openshell/v1/tcp_client_test.go | 6 +- sdk/go/openshell/v1/workspace_scope.go | 13 + sdk/go/proto/datamodelv1/datamodel.pb.go | 202 ++- sdk/go/proto/openshellv1/openshell.pb.go | 1501 +++++++---------- sdk/go/proto/sandboxv1/sandbox.pb.go | 137 +- sdk/typescript/src/client.test.ts | 84 +- sdk/typescript/src/client.ts | 45 +- skills/openshell-cli/SKILL.md | 3 + tasks/scripts/generate_python_proto.py | 6 + 61 files changed, 2944 insertions(+), 2616 deletions(-) diff --git a/architecture/gateway.md b/architecture/gateway.md index 69291eda8..c621a4b0f 100644 --- a/architecture/gateway.md +++ b/architecture/gateway.md @@ -56,13 +56,16 @@ cross-origin or sibling-subdomain request. Public workspace-scoped RPCs carry a typed `WorkspaceSelector`. A request must select one non-empty workspace explicitly; `default` is an ordinary explicit -name, not an omitted-value fallback. Sandbox, sandbox template, provider, and -service list RPCs also accept an all-workspaces marker after Platform Admin -authorization. Single-workspace handlers reject that marker. Platform-global -policy operations require the selector to be absent, while workspace policy -operations require it. The gateway authorizes the selected scope before -performing resource lookup so malformed, unsupported, and unauthorized scopes -have consistent behavior across resource types. +name, not an omitted-value fallback. Sandbox-scoped RPCs instead carry a +`SandboxReference`: name references include a named workspace selector, while +canonical ID references omit it. The gateway resolves either form to the same +persisted sandbox record and authorizes that record's workspace. Missing and +unauthorized references use the same response within each principal class so +the resolver does not expose an object-existence oracle. Sandbox, sandbox +template, provider, and service list RPCs also accept an all-workspaces marker +after Platform Admin authorization. Single-workspace handlers reject that +marker. Platform-global policy operations require the sandbox reference to be +absent, while sandbox policy operations require it. Docker and Podman report the local address through which their sandboxes can reach the gateway. When the primary listener covers that address, the gateway diff --git a/crates/openshell-cli/src/commands/provider.rs b/crates/openshell-cli/src/commands/provider.rs index e966eb690..1cd5cfd97 100644 --- a/crates/openshell-cli/src/commands/provider.rs +++ b/crates/openshell-cli/src/commands/provider.rs @@ -58,8 +58,9 @@ pub async fn sandbox_provider_list( let mut client = grpc_client(server, tls).await?; let response = client .list_sandbox_providers(ListSandboxProvidersRequest { - sandbox_name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, name, + )), }) .await .into_diagnostic()?; @@ -90,8 +91,9 @@ pub async fn sandbox_provider_attach( // Fetch current sandbox to get resource_version for CAS let sandbox = client .get_sandbox(GetSandboxRequest { - name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, name, + )), }) .await .into_diagnostic()? @@ -103,10 +105,11 @@ pub async fn sandbox_provider_attach( let response = match client .attach_sandbox_provider(AttachSandboxProviderRequest { - sandbox_name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, name, + )), provider_name: provider.to_string(), expected_resource_version: resource_version, - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await { @@ -146,8 +149,9 @@ pub async fn sandbox_provider_detach( // Fetch current sandbox to get resource_version for CAS let sandbox = client .get_sandbox(GetSandboxRequest { - name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, name, + )), }) .await .into_diagnostic()? @@ -159,10 +163,11 @@ pub async fn sandbox_provider_detach( let response = match client .detach_sandbox_provider(DetachSandboxProviderRequest { - sandbox_name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, name, + )), provider_name: provider.to_string(), expected_resource_version: resource_version, - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await { diff --git a/crates/openshell-cli/src/main.rs b/crates/openshell-cli/src/main.rs index db18795b6..f2551b3ac 100644 --- a/crates/openshell-cli/src/main.rs +++ b/crates/openshell-cli/src/main.rs @@ -2137,7 +2137,7 @@ enum ServiceCommands { offset: u32, /// List services across all workspaces (overrides --workspace). - #[arg(long)] + #[arg(long, conflicts_with = "sandbox")] all_workspaces: bool, /// Output format. @@ -6059,6 +6059,19 @@ mod tests { } } + #[test] + fn service_list_rejects_sandbox_with_all_workspaces() { + let result = Cli::try_parse_from([ + "openshell", + "service", + "list", + "my-sandbox", + "--all-workspaces", + ]); + + assert!(result.is_err()); + } + #[test] fn service_get_accepts_optional_service_name() { let cli = Cli::try_parse_from(["openshell", "service", "get", "my-sandbox", "api"]) diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index 3421f0f87..867a2e4cb 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -686,10 +686,12 @@ pub async fn sandbox_create( let setting = parse_cli_setting_value(settings::PROPOSAL_APPROVAL_MODE_KEY, approval_mode)?; match client .update_config(UpdateConfigRequest { - name: sandbox_name.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + sandbox_name.clone(), + )), setting_key: settings::PROPOSAL_APPROVAL_MODE_KEY.to_string(), setting_value: Some(setting), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), ..Default::default() }) .await @@ -752,7 +754,9 @@ pub async fn sandbox_create( }; let mut stream = client .watch_sandbox(WatchSandboxRequest { - id: sandbox_id.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox_id.clone(), + )), follow_status: true, follow_logs: true, follow_events: true, @@ -1494,8 +1498,10 @@ pub async fn sandbox_get( let response = client .get_sandbox(GetSandboxRequest { - name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), }) .await .into_diagnostic()?; @@ -1511,7 +1517,9 @@ pub async fn sandbox_get( }; let config = client - .get_sandbox_config(GetSandboxConfigRequest { sandbox_id }) + .get_sandbox_config(GetSandboxConfigRequest { + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)), + }) .await .into_diagnostic()? .into_inner(); @@ -1656,8 +1664,10 @@ pub async fn sandbox_exec_grpc( // Resolve sandbox name to id. let sandbox = client .get_sandbox(GetSandboxRequest { - name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), }) .await .into_diagnostic()? @@ -1725,7 +1735,9 @@ pub async fn sandbox_exec_grpc( // Make the streaming gRPC call. let mut stream = client .exec_sandbox(ExecSandboxRequest { - sandbox_id: sandbox.object_id().to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox.object_id(), + )), command: command.to_vec(), workdir: workdir.unwrap_or_default().to_string(), environment: environment.clone(), @@ -1876,7 +1888,7 @@ async fn create_forward_session_token( ) -> std::result::Result { let response = client .create_ssh_session(CreateSshSessionRequest { - sandbox_id: sandbox_id.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)), }) .await .map_err(ForwardTcpConnectionError::from_status)?; @@ -1890,8 +1902,10 @@ async fn fetch_ready_sandbox_for_forward( ) -> Result { let response = match client .get_sandbox(GetSandboxRequest { - name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), }) .await { @@ -1989,7 +2003,7 @@ async fn forward_one_tcp_connection( tx.send(TcpForwardFrame { payload: Some(openshell_core::proto::tcp_forward_frame::Payload::Init( TcpForwardInit { - sandbox_id, + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)), service_id, target: Some(tcp_forward_init::Target::Tcp(TcpRelayTarget { host: target_host, @@ -2109,7 +2123,9 @@ async fn sandbox_exec_interactive_grpc( input_tx .send(ExecSandboxInput { payload: Some(exec_sandbox_input::Payload::Start(ExecSandboxRequest { - sandbox_id: sandbox.object_id().to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox.object_id(), + )), command: command.to_vec(), workdir: workdir.unwrap_or_default().to_string(), environment: environment.clone(), @@ -3065,8 +3081,10 @@ pub async fn sandbox_delete( let response = match client .delete_sandbox(DeleteSandboxRequest { - name: name.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.clone(), + )), }) .await { @@ -3117,8 +3135,10 @@ pub async fn sandbox_stop( let mut client = grpc_client(server, tls).await?; let sandbox = client .stop_sandbox(StopSandboxRequest { - name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), }) .await .into_diagnostic()? @@ -3140,8 +3160,10 @@ pub async fn sandbox_start( let mut client = grpc_client(server, tls).await?; let sandbox = client .start_sandbox(StartSandboxRequest { - name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), }) .await .into_diagnostic()? @@ -3177,7 +3199,7 @@ async fn wait_for_lifecycle_phase( let sandbox_id = sandbox.object_id().to_string(); let mut stream = client .watch_sandbox(WatchSandboxRequest { - id: sandbox_id, + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)), follow_status: true, follow_logs: false, follow_events: false, @@ -3238,11 +3260,12 @@ pub async fn service_expose( let mut client = grpc_client(server, tls).await?; let response = client .expose_service(ExposeServiceRequest { - sandbox: sandbox.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, sandbox, + )), service: service.to_string(), target_port: u32::from(target_port), domain: true, - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await .map_err(service_expose_status_error)? @@ -3289,13 +3312,16 @@ pub async fn service_list( let mut client = grpc_client(server, tls).await?; let response = client .list_services(ListServicesRequest { - sandbox: sandbox.unwrap_or_default().to_string(), + sandbox_ref: sandbox + .map(|name| openshell_core::proto::sandbox_reference_by_name(workspace, name)), limit, offset, - workspace_scope: Some(if all_workspaces { - openshell_core::proto::all_workspaces_selector() - } else { - openshell_core::proto::workspace_selector(workspace) + workspace_scope: sandbox.is_none().then(|| { + if all_workspaces { + openshell_core::proto::all_workspaces_selector() + } else { + openshell_core::proto::workspace_selector(workspace) + } }), }) .await @@ -3334,9 +3360,10 @@ pub async fn service_get( let mut client = grpc_client(server, tls).await?; let response = client .get_service(GetServiceRequest { - sandbox: sandbox.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, sandbox, + )), service: service.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await .map_err(|status| service_status_error("get service", "sandbox:read", status))? @@ -3356,9 +3383,10 @@ pub async fn service_delete( let mut client = grpc_client(server, tls).await?; let response = client .delete_service(DeleteServiceRequest { - sandbox: sandbox.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, sandbox, + )), service: service.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await .map_err(|status| service_status_error("delete service", "sandbox:write", status))? @@ -4152,7 +4180,7 @@ pub async fn sandbox_policy_set_global( let mut client = grpc_client(server, tls).await?; let response = client .update_config(UpdateConfigRequest { - name: String::new(), + sandbox_ref: None, policy: Some(policy), global: true, ..Default::default() @@ -4184,8 +4212,10 @@ pub async fn sandbox_settings_get( let mut client = grpc_client(server, tls).await?; let sandbox = client .get_sandbox(GetSandboxRequest { - name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), }) .await .into_diagnostic()? @@ -4195,7 +4225,9 @@ pub async fn sandbox_settings_get( let response = client .get_sandbox_config(GetSandboxConfigRequest { - sandbox_id: sandbox.object_id().to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox.object_id(), + )), }) .await .into_diagnostic()? @@ -4352,7 +4384,7 @@ pub async fn gateway_setting_set( let mut client = grpc_client(server, tls).await?; let response = client .update_config(UpdateConfigRequest { - name: String::new(), + sandbox_ref: None, setting_key: key.to_string(), setting_value: Some(setting_value), global: true, @@ -4385,10 +4417,12 @@ pub async fn sandbox_setting_set( let mut client = grpc_client(server, tls).await?; let response = client .update_config(UpdateConfigRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), setting_key: key.to_string(), setting_value: Some(setting_value), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), ..Default::default() }) .await @@ -4418,7 +4452,7 @@ pub async fn gateway_setting_delete( let mut client = grpc_client(server, tls).await?; let response = client .update_config(UpdateConfigRequest { - name: String::new(), + sandbox_ref: None, setting_key: key.to_string(), delete_setting: true, global: true, @@ -4451,10 +4485,12 @@ pub async fn sandbox_setting_delete( let mut client = grpc_client(server, tls).await?; let response = client .update_config(UpdateConfigRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), setting_key: key.to_string(), delete_setting: true, - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), ..Default::default() }) .await @@ -4497,10 +4533,12 @@ pub async fn sandbox_policy_set( // Get current version so we can detect no-ops. let current_version = client .get_sandbox_policy_status(GetSandboxPolicyStatusRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), version: 0, global: false, - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await .ok() @@ -4509,9 +4547,11 @@ pub async fn sandbox_policy_set( let response = client .update_config(UpdateConfigRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), policy: Some(policy), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), ..Default::default() }) .await @@ -4556,10 +4596,12 @@ pub async fn sandbox_policy_set( let status_resp = client .get_sandbox_policy_status(GetSandboxPolicyStatusRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), version: resp.version, global: false, - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await .into_diagnostic()?; @@ -4635,8 +4677,10 @@ pub async fn sandbox_policy_update( let mut client = grpc_client(server, tls).await?; let sandbox = client .get_sandbox(GetSandboxRequest { - name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), }) .await .into_diagnostic()? @@ -4651,7 +4695,9 @@ pub async fn sandbox_policy_update( }; let current = client - .get_sandbox_config(GetSandboxConfigRequest { sandbox_id }) + .get_sandbox_config(GetSandboxConfigRequest { + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)), + }) .await .into_diagnostic()? .into_inner(); @@ -4683,9 +4729,11 @@ pub async fn sandbox_policy_update( let current_hash = current.policy_hash.clone(); let response = client .update_config(UpdateConfigRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), merge_operations: plan.merge_operations, - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), ..Default::default() }) .await @@ -4730,10 +4778,12 @@ pub async fn sandbox_policy_update( let status_resp = client .get_sandbox_policy_status(GetSandboxPolicyStatusRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), version: response.version, global: false, - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await .into_diagnostic()?; @@ -4838,10 +4888,12 @@ where let status_resp = client .get_sandbox_policy_status(GetSandboxPolicyStatusRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), version, global: false, - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await .into_diagnostic()?; @@ -4922,8 +4974,10 @@ where let sandbox = client .get_sandbox(GetSandboxRequest { - name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), }) .await .into_diagnostic()? @@ -4937,7 +4991,7 @@ where let config = client .get_sandbox_config(GetSandboxConfigRequest { - sandbox_id: sandbox_id.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)), }) .await .into_diagnostic()? @@ -5032,10 +5086,9 @@ pub async fn sandbox_policy_get_global( let status_resp = client .get_sandbox_policy_status(GetSandboxPolicyStatusRequest { - name: String::new(), + sandbox_ref: None, version, global: true, - workspace_scope: None, }) .await .into_diagnostic()?; @@ -5172,11 +5225,13 @@ pub async fn sandbox_policy_list( let resp = client .list_sandbox_policies(ListSandboxPoliciesRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), limit, offset: 0, global: false, - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await .into_diagnostic()?; @@ -5207,11 +5262,10 @@ pub async fn sandbox_policy_list_global( let resp = client .list_sandbox_policies(ListSandboxPoliciesRequest { - name: String::new(), + sandbox_ref: None, limit, offset: 0, global: true, - workspace_scope: None, }) .await .into_diagnostic()?; @@ -5302,8 +5356,10 @@ pub async fn sandbox_logs( // Resolve sandbox name to id. let sandbox = client .get_sandbox(GetSandboxRequest { - name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), }) .await .into_diagnostic()? @@ -5336,7 +5392,9 @@ pub async fn sandbox_logs( // Streaming mode: use WatchSandbox. let mut stream = client .watch_sandbox(WatchSandboxRequest { - id: sandbox.object_id().to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox.object_id().to_string(), + )), follow_status: false, follow_logs: true, follow_events: false, @@ -5363,12 +5421,13 @@ pub async fn sandbox_logs( // One-shot mode: use GetSandboxLogs. let resp = client .get_sandbox_logs(GetSandboxLogsRequest { - sandbox_id: sandbox.object_id().to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox.object_id(), + )), lines, since_ms, sources: source_filter, min_level: level.to_uppercase(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await .into_diagnostic()?; @@ -5442,9 +5501,11 @@ pub async fn sandbox_draft_get( let response = client .get_draft_policy(GetDraftPolicyRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), status_filter: status_filter.unwrap_or("").to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await .into_diagnostic()?; @@ -5550,9 +5611,11 @@ pub async fn sandbox_draft_approve( let mut client = grpc_client(server, tls).await?; let review_token = client .get_draft_policy(GetDraftPolicyRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await .into_diagnostic()? @@ -5565,9 +5628,11 @@ pub async fn sandbox_draft_approve( let response = client .approve_draft_chunk(ApproveDraftChunkRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), chunk_id: chunk_id.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), review_token, }) .await @@ -5597,10 +5662,12 @@ pub async fn sandbox_draft_reject( client .reject_draft_chunk(RejectDraftChunkRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), chunk_id: chunk_id.to_string(), reason: reason.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await .into_diagnostic()?; @@ -5621,9 +5688,11 @@ pub async fn sandbox_draft_approve_all( let mut client = grpc_client(server, tls).await?; let approvals = client .get_draft_policy(GetDraftPolicyRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), status_filter: "pending".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), }) .await .into_diagnostic()? @@ -5638,9 +5707,11 @@ pub async fn sandbox_draft_approve_all( let response = client .approve_all_draft_chunks(ApproveAllDraftChunksRequest { - name: name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), include_security_flagged, - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), approvals, }) .await @@ -5669,8 +5740,10 @@ pub async fn sandbox_draft_clear( let response = client .clear_draft_chunks(ClearDraftChunksRequest { - name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), }) .await .into_diagnostic()?; @@ -5696,8 +5769,10 @@ pub async fn sandbox_draft_history( let response = client .get_draft_history(GetDraftHistoryRequest { - name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), }) .await .into_diagnostic()?; diff --git a/crates/openshell-cli/src/ssh.rs b/crates/openshell-cli/src/ssh.rs index c0937db48..98a31d946 100644 --- a/crates/openshell-cli/src/ssh.rs +++ b/crates/openshell-cli/src/ssh.rs @@ -91,8 +91,10 @@ async fn ssh_session_config( // Resolve sandbox name to id. let sandbox = client .get_sandbox(GetSandboxRequest { - name: name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + name.to_string(), + )), }) .await .into_diagnostic()? @@ -105,7 +107,9 @@ async fn ssh_session_config( let response = loop { match client .create_ssh_session(CreateSshSessionRequest { - sandbox_id: sandbox.object_id().to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox.object_id(), + )), }) .await { @@ -1436,7 +1440,7 @@ pub async fn sandbox_ssh_proxy( tx.send(TcpForwardFrame { payload: Some(openshell_core::proto::tcp_forward_frame::Payload::Init( TcpForwardInit { - sandbox_id: sandbox_id.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)), service_id: format!("ssh-proxy:{sandbox_id}"), target: Some(tcp_forward_init::Target::Ssh(SshRelayTarget {})), authorization_token: token.to_string(), diff --git a/crates/openshell-cli/tests/provider_commands_integration.rs b/crates/openshell-cli/tests/provider_commands_integration.rs index 912835faa..f4ba46dab 100644 --- a/crates/openshell-cli/tests/provider_commands_integration.rs +++ b/crates/openshell-cli/tests/provider_commands_integration.rs @@ -39,6 +39,13 @@ use tokio_stream::wrappers::TcpListenerStream; use tonic::transport::{Certificate as TlsCertificate, Identity, Server, ServerTlsConfig}; use tonic::{Response, Status}; +fn sandbox_reference_name(sandbox_ref: Option<&openshell_core::proto::SandboxReference>) -> String { + match sandbox_ref.and_then(|reference| reference.identifier.as_ref()) { + Some(openshell_core::proto::sandbox_reference::Identifier::Name(name)) => name.clone(), + _ => panic!("expected sandbox name reference"), + } +} + #[derive(Clone, Default)] struct ProviderState { providers: Arc>>, @@ -174,7 +181,8 @@ impl OpenShell for TestOpenShell { &self, request: tonic::Request, ) -> Result, Status> { - let name = request.into_inner().name; + let request = request.into_inner(); + let name = sandbox_reference_name(request.sandbox_ref.as_ref()); // Return a minimal sandbox with metadata for CAS operations Ok(Response::new(SandboxResponse { sandbox: Some(Sandbox { @@ -208,7 +216,8 @@ impl OpenShell for TestOpenShell { &self, request: tonic::Request, ) -> Result, Status> { - let sandbox_name = request.into_inner().sandbox_name; + let request = request.into_inner(); + let sandbox_name = sandbox_reference_name(request.sandbox_ref.as_ref()); self.state .sandbox_provider_requests .lock() @@ -237,12 +246,13 @@ impl OpenShell for TestOpenShell { request: tonic::Request, ) -> Result, Status> { let request = request.into_inner(); + let sandbox_name = sandbox_reference_name(request.sandbox_ref.as_ref()); self.state .sandbox_provider_requests .lock() .await .push(SandboxProviderRequestLog::Attach { - sandbox_name: request.sandbox_name.clone(), + sandbox_name: sandbox_name.clone(), provider_name: request.provider_name.clone(), }); if !self @@ -255,9 +265,7 @@ impl OpenShell for TestOpenShell { return Err(Status::failed_precondition("provider not found")); } let mut sandbox_providers = self.state.sandbox_providers.lock().await; - let providers = sandbox_providers - .entry(request.sandbox_name.clone()) - .or_default(); + let providers = sandbox_providers.entry(sandbox_name.clone()).or_default(); let attached = if providers.contains(&request.provider_name) { false } else { @@ -266,7 +274,7 @@ impl OpenShell for TestOpenShell { }; let sandbox = Sandbox { metadata: Some(openshell_core::proto::datamodel::v1::ObjectMeta { - name: request.sandbox_name, + name: sandbox_name, ..Default::default() }), spec: Some(openshell_core::proto::SandboxSpec { @@ -286,24 +294,23 @@ impl OpenShell for TestOpenShell { request: tonic::Request, ) -> Result, Status> { let request = request.into_inner(); + let sandbox_name = sandbox_reference_name(request.sandbox_ref.as_ref()); self.state .sandbox_provider_requests .lock() .await .push(SandboxProviderRequestLog::Detach { - sandbox_name: request.sandbox_name.clone(), + sandbox_name: sandbox_name.clone(), provider_name: request.provider_name.clone(), }); let mut sandbox_providers = self.state.sandbox_providers.lock().await; - let providers = sandbox_providers - .entry(request.sandbox_name.clone()) - .or_default(); + let providers = sandbox_providers.entry(sandbox_name.clone()).or_default(); let before_len = providers.len(); providers.retain(|name| name != &request.provider_name); let detached = providers.len() != before_len; let sandbox = Sandbox { metadata: Some(openshell_core::proto::datamodel::v1::ObjectMeta { - name: request.sandbox_name, + name: sandbox_name, ..Default::default() }), spec: Some(openshell_core::proto::SandboxSpec { diff --git a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs index 6ed9a7433..7d56ba37b 100644 --- a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs +++ b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs @@ -56,6 +56,20 @@ fn selected_workspace( } } +fn sandbox_reference_name(sandbox_ref: Option<&openshell_core::proto::SandboxReference>) -> String { + match sandbox_ref.and_then(|reference| reference.identifier.as_ref()) { + Some(openshell_core::proto::sandbox_reference::Identifier::Name(name)) => name.clone(), + _ => panic!("expected sandbox name reference"), + } +} + +fn sandbox_reference_id(sandbox_ref: Option<&openshell_core::proto::SandboxReference>) -> String { + match sandbox_ref.and_then(|reference| reference.identifier.as_ref()) { + Some(openshell_core::proto::sandbox_reference::Identifier::Id(id)) => id.clone(), + _ => panic!("expected sandbox ID reference"), + } +} + #[derive(Clone, Default)] struct SandboxState { deleted_names: Arc>>>, @@ -182,7 +196,8 @@ impl OpenShell for TestOpenShell { &self, request: tonic::Request, ) -> Result, Status> { - let name = request.into_inner().name; + let request = request.into_inner(); + let name = sandbox_reference_name(request.sandbox_ref.as_ref()); let mut sandbox = Sandbox { metadata: Some(openshell_core::proto::datamodel::v1::ObjectMeta { id: format!("id-{name}"), @@ -332,7 +347,7 @@ impl OpenShell for TestOpenShell { .deleted_names .lock() .await - .push(vec![request.name.clone()]); + .push(vec![sandbox_reference_name(request.sandbox_ref.as_ref())]); let delete_failure = self.state.fail_delete_sandbox_message.lock().await.take(); if let Some(message) = delete_failure { return Err(Status::internal(message)); @@ -386,7 +401,8 @@ impl OpenShell for TestOpenShell { { return Err(Status::failed_precondition("sandbox is not ready")); } - let sandbox_id = request.into_inner().sandbox_id; + let request = request.into_inner(); + let sandbox_id = sandbox_reference_id(request.sandbox_ref.as_ref()); Ok(Response::new(CreateSshSessionResponse { sandbox_id, token: "test-token".to_string(), @@ -574,7 +590,8 @@ impl OpenShell for TestOpenShell { &self, request: tonic::Request, ) -> Result, Status> { - let sandbox_id = request.into_inner().id; + let request = request.into_inner(); + let sandbox_id = sandbox_reference_id(request.sandbox_ref.as_ref()); let (tx, rx) = mpsc::channel(4); let vm_error_after_started = self.state.vm_error_after_started.load(Ordering::SeqCst); let vm_error_with_observed_exit = self diff --git a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs index fdb0ebbd0..08fcf064c 100644 --- a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs +++ b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs @@ -32,6 +32,20 @@ use tokio_stream::wrappers::TcpListenerStream; use tonic::transport::{Certificate as TlsCertificate, Identity, Server, ServerTlsConfig}; use tonic::{Response, Status}; +fn sandbox_reference_name(sandbox_ref: Option<&openshell_core::proto::SandboxReference>) -> String { + match sandbox_ref.and_then(|reference| reference.identifier.as_ref()) { + Some(openshell_core::proto::sandbox_reference::Identifier::Name(name)) => name.clone(), + _ => panic!("expected sandbox name reference"), + } +} + +fn sandbox_reference_id(sandbox_ref: Option<&openshell_core::proto::SandboxReference>) -> String { + match sandbox_ref.and_then(|reference| reference.identifier.as_ref()) { + Some(openshell_core::proto::sandbox_reference::Identifier::Id(id)) => id.clone(), + _ => panic!("expected sandbox ID reference"), + } +} + // ── mock OpenShell server ───────────────────────────────────────────── /// Records which sandbox name was requested via `get_sandbox`. @@ -117,7 +131,8 @@ impl OpenShell for TestOpenShell { &self, request: tonic::Request, ) -> Result, Status> { - let name = request.into_inner().name; + let request = request.into_inner(); + let name = sandbox_reference_name(request.sandbox_ref.as_ref()); *self.state.last_get_name.lock().await = Some(name.clone()); Ok(Response::new(SandboxResponse { sandbox: Some(Sandbox { @@ -179,7 +194,8 @@ impl OpenShell for TestOpenShell { ) -> Result, Status> { let req = request.into_inner(); assert_eq!( - req.sandbox_id, "test-id", + sandbox_reference_id(req.sandbox_ref.as_ref()), + "test-id", "GetSandboxConfig should pass the id from GetSandbox" ); Ok(Response::new(GetSandboxConfigResponse { @@ -450,7 +466,10 @@ impl OpenShell for TestOpenShell { request: tonic::Request, ) -> Result, Status> { let req = request.into_inner(); - assert_eq!(req.name, "my-sandbox"); + assert_eq!( + sandbox_reference_name(req.sandbox_ref.as_ref()), + "my-sandbox" + ); assert_eq!(req.version, 3); assert!(!req.global); diff --git a/crates/openshell-core/src/grpc_client.rs b/crates/openshell-core/src/grpc_client.rs index 38b91e850..00920b615 100644 --- a/crates/openshell-core/src/grpc_client.rs +++ b/crates/openshell-core/src/grpc_client.rs @@ -28,7 +28,6 @@ use crate::proto::{ NetworkActivitySummary, PolicyChunk, PolicySource, PolicyStatus, RefreshSandboxTokenRequest, ReportPolicyStatusRequest, SandboxPolicy as ProtoSandboxPolicy, SubmitPolicyAnalysisRequest, SubmitPolicyAnalysisResponse, UpdateConfigRequest, open_shell_client::OpenShellClient, - workspace_selector, }; use crate::sandbox_env; use miette::{IntoDiagnostic, Result, WrapErr}; @@ -717,7 +716,7 @@ async fn fetch_settings_snapshot_with_client( ) -> Result { let response = client .get_sandbox_config(GetSandboxConfigRequest { - sandbox_id: sandbox_id.to_string(), + sandbox_ref: Some(crate::proto::sandbox_reference_by_id(sandbox_id)), }) .await .into_diagnostic()?; @@ -751,9 +750,8 @@ async fn sync_policy_with_client( ) -> Result<()> { client .update_config(UpdateConfigRequest { - name: sandbox.to_string(), + sandbox_ref: Some(crate::proto::sandbox_reference_by_name(workspace, sandbox)), policy: Some(policy.clone()), - workspace_scope: Some(workspace_selector(workspace)), ..Default::default() }) .await @@ -1053,7 +1051,7 @@ impl CachedOpenShellClient { .client .clone() .get_sandbox_config(GetSandboxConfigRequest { - sandbox_id: sandbox_id.to_string(), + sandbox_ref: Some(crate::proto::sandbox_reference_by_id(sandbox_id)), }) .await .into_diagnostic()?; @@ -1179,9 +1177,11 @@ impl CachedOpenShellClient { .client .clone() .get_draft_policy(GetDraftPolicyRequest { - name: sandbox_name.to_string(), status_filter: status_filter.to_string(), - workspace_scope: Some(workspace_selector(self.workspace())), + sandbox_ref: Some(crate::proto::sandbox_reference_by_name( + self.workspace(), + sandbox_name, + )), }) .await .into_diagnostic()?; diff --git a/crates/openshell-core/src/proto/mod.rs b/crates/openshell-core/src/proto/mod.rs index d0b976ff2..d9032c97b 100644 --- a/crates/openshell-core/src/proto/mod.rs +++ b/crates/openshell-core/src/proto/mod.rs @@ -89,3 +89,22 @@ pub fn all_workspaces_selector() -> WorkspaceSelector { )), } } + +/// Build a canonical reference from a stable sandbox ID. +pub fn sandbox_reference_by_id(id: impl Into) -> SandboxReference { + SandboxReference { + identifier: Some(sandbox_reference::Identifier::Id(id.into())), + workspace_scope: None, + } +} + +/// Build a canonical reference from a workspace-scoped sandbox name. +pub fn sandbox_reference_by_name( + workspace: impl Into, + name: impl Into, +) -> SandboxReference { + SandboxReference { + identifier: Some(sandbox_reference::Identifier::Name(name.into())), + workspace_scope: Some(workspace_selector(workspace)), + } +} diff --git a/crates/openshell-gateway-interceptors/src/proto_json.rs b/crates/openshell-gateway-interceptors/src/proto_json.rs index 3f79be72f..0aae1a314 100644 --- a/crates/openshell-gateway-interceptors/src/proto_json.rs +++ b/crates/openshell-gateway-interceptors/src/proto_json.rs @@ -291,7 +291,7 @@ mod tests { use openshell_core::proto::{ CreateProviderRequest, CreateSandboxRequest, GpuResourceRequirements, Provider, - SandboxSpec, UpdateConfigRequest, workspace_selector, + SandboxSpec, UpdateConfigRequest, sandbox_reference_by_name, workspace_selector, }; use prost::Message as _; use prost_types::{ @@ -454,12 +454,11 @@ mod tests { let codec = ProtoJsonCodec::from_descriptor_set(openshell_core::FILE_DESCRIPTOR_SET).unwrap(); let request = UpdateConfigRequest { - name: "demo".to_string(), + sandbox_ref: Some(sandbox_reference_by_name("default", "demo")), annotations: HashMap::from([( "openshell.nvidia.com/policy-signature".to_string(), "signed".to_string(), )]), - workspace_scope: Some(workspace_selector("default")), ..Default::default() }; let bytes = request.encode_to_vec(); diff --git a/crates/openshell-gateway-interceptors/src/runtime.rs b/crates/openshell-gateway-interceptors/src/runtime.rs index 0bd1ba2b4..46f1c990c 100644 --- a/crates/openshell-gateway-interceptors/src/runtime.rs +++ b/crates/openshell-gateway-interceptors/src/runtime.rs @@ -1009,7 +1009,9 @@ mod tests { codec: codec.clone(), }; let request = UpdateConfigRequest { - name: "demo".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", "demo", + )), expected_resource_version: u64::MAX - 1, annotations: HashMap::from([ ("policy-hash".to_string(), "sha256:v2:abc".to_string()), @@ -1017,7 +1019,6 @@ mod tests { ("policy-signature-kid".to_string(), "kid".to_string()), ("correlation-id".to_string(), "reload-1".to_string()), ]), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..UpdateConfigRequest::default() }; let body = GrpcFrame { @@ -1186,7 +1187,10 @@ mod tests { let operation = ValidatedOperation::new( &codec, "openshell.v1.UpdateConfigRequest", - json!({"name": "demo", "expectedResourceVersion": "7"}), + json!({ + "sandboxRef": {"name": "demo", "workspaceScope": {"workspace": "default"}}, + "expectedResourceVersion": "7" + }), ) .unwrap(); let prior = operation.clone(); @@ -1224,7 +1228,10 @@ mod tests { let operation = ValidatedOperation::new( &codec, "openshell.v1.UpdateConfigRequest", - json!({"name": "demo", "expectedResourceVersion": "7"}), + json!({ + "sandboxRef": {"name": "demo", "workspaceScope": {"workspace": "default"}}, + "expectedResourceVersion": "7" + }), ) .unwrap(); let plan = test_modify_plan(FailurePolicy::FailClosed); @@ -1262,12 +1269,15 @@ mod tests { let operation = ValidatedOperation::new( &codec, "openshell.v1.UpdateConfigRequest", - json!({"name": "demo", "expectedResourceVersion": "7"}), + json!({ + "sandboxRef": {"name": "demo", "workspaceScope": {"workspace": "default"}}, + "expectedResourceVersion": "7" + }), ) .unwrap(); let prior = operation.clone(); let result = allowed_result(vec![ - patch("replace", "/name", json!("partially-mutated")), + patch("replace", "/sandboxRef/name", json!("partially-mutated")), patch("replace", "/expectedResourceVersion", json!("not-a-number")), ]); @@ -1281,7 +1291,7 @@ mod tests { .unwrap(); assert_eq!(outcome, prior); - assert_eq!(outcome.json["name"], "demo"); + assert_eq!(outcome.json["sandboxRef"]["name"], "demo"); } #[tokio::test] @@ -1292,17 +1302,20 @@ mod tests { let operation = ValidatedOperation::new( &codec, "openshell.v1.UpdateConfigRequest", - json!({"name": "demo", "expectedResourceVersion": "7"}), + json!({ + "sandboxRef": {"name": "demo", "workspaceScope": {"workspace": "default"}}, + "expectedResourceVersion": "7" + }), ) .unwrap(); let plan = test_modify_plan(FailurePolicy::FailOpen); let invalid_first = allowed_result(vec![ - patch("replace", "/name", json!("rejected-candidate")), + patch("replace", "/sandboxRef/name", json!("rejected-candidate")), patch("replace", "/expectedResourceVersion", json!("not-a-number")), ]); let second = allowed_result(vec![ - patch("test", "/name", json!("demo")), - patch("replace", "/name", json!("accepted-candidate")), + patch("test", "/sandboxRef/name", json!("demo")), + patch("replace", "/sandboxRef/name", json!("accepted-candidate")), ]); let operation = apply_evaluation_result( @@ -1322,9 +1335,14 @@ mod tests { ) .unwrap(); - assert_eq!(operation.json["name"], "accepted-candidate"); + assert_eq!(operation.json["sandboxRef"]["name"], "accepted-candidate"); let decoded = UpdateConfigRequest::decode(operation.encoded.as_slice()).unwrap(); - assert_eq!(decoded.name, "accepted-candidate"); + assert_eq!( + decoded.sandbox_ref.unwrap().identifier, + Some(openshell_core::proto::sandbox_reference::Identifier::Name( + "accepted-candidate".to_string() + )) + ); assert_eq!(decoded.expected_resource_version, 7); } @@ -1371,10 +1389,17 @@ mod tests { let operation = ValidatedOperation::new( &codec, "openshell.v1.UpdateConfigRequest", - json!({"name": "demo", "expectedResourceVersion": "7"}), + json!({ + "sandboxRef": {"name": "demo", "workspaceScope": {"workspace": "default"}}, + "expectedResourceVersion": "7" + }), ) .unwrap(); - let result = allowed_result(vec![patch("replace", "/name", json!("accepted"))]); + let result = allowed_result(vec![patch( + "replace", + "/sandboxRef/name", + json!("accepted"), + )]); let recorder = TestRecorder::default(); let operation = metrics::with_local_recorder(&recorder, || { @@ -1389,7 +1414,12 @@ mod tests { .unwrap(); let decoded = UpdateConfigRequest::decode(operation.encoded.as_slice()).unwrap(); - assert_eq!(decoded.name, "accepted"); + assert_eq!( + decoded.sandbox_ref.unwrap().identifier, + Some(openshell_core::proto::sandbox_reference::Identifier::Name( + "accepted".to_string() + )) + ); assert_eq!(TestRecorder::count(&recorder.evaluations), 1); assert_eq!(TestRecorder::count(&recorder.patches), 1); assert_eq!(TestRecorder::count(&recorder.fail_open), 0); diff --git a/crates/openshell-sdk/src/client.rs b/crates/openshell-sdk/src/client.rs index 4713f0ed1..045893142 100644 --- a/crates/openshell-sdk/src/client.rs +++ b/crates/openshell-sdk/src/client.rs @@ -248,8 +248,7 @@ impl OpenShellClient { let response = self .unary(|mut grpc| { let request = proto::GetSandboxRequest { - name: name.to_string(), - workspace_scope: Some(proto::workspace_selector("default")), + sandbox_ref: Some(proto::sandbox_reference_by_name("default", name)), }; async move { grpc.get_sandbox(request).await } }) @@ -287,8 +286,7 @@ impl OpenShellClient { let response = self .unary(|mut grpc| { let request = proto::DeleteSandboxRequest { - name: name.to_string(), - workspace_scope: Some(proto::workspace_selector("default")), + sandbox_ref: Some(proto::sandbox_reference_by_name("default", name)), }; async move { grpc.delete_sandbox(request).await } }) @@ -301,8 +299,7 @@ impl OpenShellClient { let response = self .unary(|mut grpc| { let request = proto::StopSandboxRequest { - name: name.to_string(), - workspace_scope: Some(proto::workspace_selector("default")), + sandbox_ref: Some(proto::sandbox_reference_by_name("default", name)), }; async move { grpc.stop_sandbox(request).await } }) @@ -315,8 +312,7 @@ impl OpenShellClient { let response = self .unary(|mut grpc| { let request = proto::StartSandboxRequest { - name: name.to_string(), - workspace_scope: Some(proto::workspace_selector("default")), + sandbox_ref: Some(proto::sandbox_reference_by_name("default", name)), }; async move { grpc.start_sandbox(request).await } }) @@ -478,7 +474,7 @@ impl OpenShellClient { pub async fn exec(&self, name: &str, cmd: &[String], opts: ExecOptions) -> Result { let sandbox = self.get_sandbox(name).await?; let request = proto::ExecSandboxRequest { - sandbox_id: sandbox.id, + sandbox_ref: Some(proto::sandbox_reference_by_id(sandbox.id)), command: cmd.to_vec(), workdir: opts.workdir.unwrap_or_default(), environment: opts.environment, @@ -736,8 +732,7 @@ impl WorkspaceScopedClient { .client .unary(|mut grpc| { let request = proto::GetSandboxRequest { - name: name.to_string(), - workspace_scope: Some(proto::workspace_selector(&self.workspace)), + sandbox_ref: Some(proto::sandbox_reference_by_name(&self.workspace, name)), }; async move { grpc.get_sandbox(request).await } }) @@ -772,8 +767,7 @@ impl WorkspaceScopedClient { .client .unary(|mut grpc| { let request = proto::DeleteSandboxRequest { - name: name.to_string(), - workspace_scope: Some(proto::workspace_selector(&self.workspace)), + sandbox_ref: Some(proto::sandbox_reference_by_name(&self.workspace, name)), }; async move { grpc.delete_sandbox(request).await } }) @@ -787,8 +781,7 @@ impl WorkspaceScopedClient { .client .unary(|mut grpc| { let request = proto::StopSandboxRequest { - name: name.to_string(), - workspace_scope: Some(proto::workspace_selector(&self.workspace)), + sandbox_ref: Some(proto::sandbox_reference_by_name(&self.workspace, name)), }; async move { grpc.stop_sandbox(request).await } }) @@ -802,8 +795,7 @@ impl WorkspaceScopedClient { .client .unary(|mut grpc| { let request = proto::StartSandboxRequest { - name: name.to_string(), - workspace_scope: Some(proto::workspace_selector(&self.workspace)), + sandbox_ref: Some(proto::sandbox_reference_by_name(&self.workspace, name)), }; async move { grpc.start_sandbox(request).await } }) @@ -868,7 +860,7 @@ impl WorkspaceScopedClient { pub async fn exec(&self, name: &str, cmd: &[String], opts: ExecOptions) -> Result { let sandbox = self.get_sandbox(name).await?; let request = proto::ExecSandboxRequest { - sandbox_id: sandbox.id, + sandbox_ref: Some(proto::sandbox_reference_by_id(sandbox.id)), command: cmd.to_vec(), workdir: opts.workdir.unwrap_or_default(), environment: opts.environment, diff --git a/crates/openshell-sdk/tests/client_mock.rs b/crates/openshell-sdk/tests/client_mock.rs index 5fd2133b5..8a1dc2576 100644 --- a/crates/openshell-sdk/tests/client_mock.rs +++ b/crates/openshell-sdk/tests/client_mock.rs @@ -39,6 +39,24 @@ fn selects_all_workspaces(scope: &Option) -> Option<&str> { + match reference.as_ref()?.identifier.as_ref()? { + proto::sandbox_reference::Identifier::Name(name) => Some(name), + proto::sandbox_reference::Identifier::Id(_) => None, + } +} + +fn sandbox_reference_id(reference: &Option) -> Option<&str> { + match reference.as_ref()?.identifier.as_ref()? { + proto::sandbox_reference::Identifier::Id(id) => Some(id), + proto::sandbox_reference::Identifier::Name(_) => None, + } +} + +fn sandbox_reference_workspace(reference: &Option) -> Option<&str> { + selected_workspace(&reference.as_ref()?.workspace_scope) +} + /// Captured fixture state — what the mock observed and the canned replies it /// returned. One per test so assertions are scoped. #[derive(Default)] @@ -297,9 +315,9 @@ impl OpenShell for TestOpenShell { ) -> Result, Status> { let request = request.into_inner(); let sandbox = sandbox_with_phase_ws( - &request.name, + sandbox_reference_name(&request.sandbox_ref).unwrap_or_default(), proto::SandboxPhase::Stopped, - selected_workspace(&request.workspace_scope).unwrap_or("default"), + sandbox_reference_workspace(&request.sandbox_ref).unwrap_or("default"), ); *self.state.last_stop.lock().await = Some(request); Ok(Response::new(proto::SandboxResponse { @@ -313,9 +331,9 @@ impl OpenShell for TestOpenShell { ) -> Result, Status> { let request = request.into_inner(); let sandbox = sandbox_with_phase_ws( - &request.name, + sandbox_reference_name(&request.sandbox_ref).unwrap_or_default(), proto::SandboxPhase::Starting, - selected_workspace(&request.workspace_scope).unwrap_or("default"), + sandbox_reference_workspace(&request.sandbox_ref).unwrap_or("default"), ); *self.state.last_start.lock().await = Some(request); Ok(Response::new(proto::SandboxResponse { @@ -328,10 +346,12 @@ impl OpenShell for TestOpenShell { request: tonic::Request, ) -> Result, Status> { let req = request.into_inner(); - let name = req.name; + let name = sandbox_reference_name(&req.sandbox_ref) + .unwrap_or_default() + .to_string(); *self.state.last_get_name.lock().await = Some(name.clone()); *self.state.last_get_workspace.lock().await = - selected_workspace(&req.workspace_scope).map(str::to_string); + sandbox_reference_workspace(&req.sandbox_ref).map(str::to_string); let count = self.state.get_calls.fetch_add(1, Ordering::SeqCst); if self.state.get_returns_not_found { @@ -399,9 +419,10 @@ impl OpenShell for TestOpenShell { request: tonic::Request, ) -> Result, Status> { let req = request.into_inner(); - *self.state.last_delete_name.lock().await = Some(req.name); + *self.state.last_delete_name.lock().await = + sandbox_reference_name(&req.sandbox_ref).map(str::to_string); *self.state.last_delete_workspace.lock().await = - selected_workspace(&req.workspace_scope).map(str::to_string); + sandbox_reference_workspace(&req.sandbox_ref).map(str::to_string); Ok(Response::new(proto::DeleteSandboxResponse { deleted: true, })) @@ -1104,8 +1125,11 @@ async fn stop_and_start_map_requests_and_phases() { let stopped = client.stop_sandbox("sleepy").await.unwrap(); assert_eq!(stopped.phase, SandboxPhase::Stopped); let stop = state.last_stop.lock().await.clone().unwrap(); - assert_eq!(stop.name, "sleepy"); - assert_eq!(selected_workspace(&stop.workspace_scope), Some("default")); + assert_eq!(sandbox_reference_name(&stop.sandbox_ref), Some("sleepy")); + assert_eq!( + sandbox_reference_workspace(&stop.sandbox_ref), + Some("default") + ); let started = client .workspace("team-a") @@ -1114,8 +1138,11 @@ async fn stop_and_start_map_requests_and_phases() { .unwrap(); assert_eq!(started.phase, SandboxPhase::Starting); let start = state.last_start.lock().await.clone().unwrap(); - assert_eq!(start.name, "sleepy"); - assert_eq!(selected_workspace(&start.workspace_scope), Some("team-a")); + assert_eq!(sandbox_reference_name(&start.sandbox_ref), Some("sleepy")); + assert_eq!( + sandbox_reference_workspace(&start.sandbox_ref), + Some("team-a") + ); } #[tokio::test] @@ -1247,7 +1274,10 @@ async fn exec_buffers_stdout_stderr_and_exit() { assert_eq!(result.stderr, b"warn\n"); let observed = state.last_exec_request.lock().await.clone().unwrap(); - assert_eq!(observed.sandbox_id, "id-my-box"); + assert_eq!( + sandbox_reference_id(&observed.sandbox_ref), + Some("id-my-box") + ); assert_eq!( observed.command, vec!["echo".to_string(), "hello".to_string()] diff --git a/crates/openshell-server/src/grpc/auth_rpc.rs b/crates/openshell-server/src/grpc/auth_rpc.rs index 62547cc29..509a5b8fd 100644 --- a/crates/openshell-server/src/grpc/auth_rpc.rs +++ b/crates/openshell-server/src/grpc/auth_rpc.rs @@ -164,7 +164,9 @@ pub async fn handle_refresh_sandbox_token( )); } else { let mut config_request = Request::new(GetSandboxConfigRequest { - sandbox_id: sandbox.sandbox_id.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + &sandbox.sandbox_id, + )), }); config_request .extensions_mut() diff --git a/crates/openshell-server/src/grpc/policy.rs b/crates/openshell-server/src/grpc/policy.rs index c2a19107e..e909cdb1e 100644 --- a/crates/openshell-server/src/grpc/policy.rs +++ b/crates/openshell-server/src/grpc/policy.rs @@ -12,10 +12,7 @@ use crate::ServerState; use crate::auth::principal::Principal; -use crate::auth::workspace_authz::{ - MinWorkspaceRole, authorize_sandbox_workspace, authorize_workspace_selector, - require_platform_admin, selected_workspace_name, -}; +use crate::auth::workspace_authz::{MinWorkspaceRole, require_platform_admin}; use crate::persistence::{ DraftChunkRecord, ObjectId, ObjectName, ObjectType, ObjectWorkspace, PolicyRecord, Store, }; @@ -2213,7 +2210,7 @@ fn validate_sandbox_caller_update(req: &UpdateConfigRequest) -> Result<(), Statu "sandbox callers cannot delete settings", )); } - if req.name.trim().is_empty() { + if req.sandbox_ref.is_none() { return Err(Status::permission_denied( "sandbox callers may only perform sandbox policy sync", )); @@ -2362,12 +2359,15 @@ pub(super) async fn handle_get_sandbox_config( request: Request, ) -> Result, Status> { let principal = super::extract_principal(&request)?; - let sandbox_id = request.get_ref().sandbox_id.clone(); - crate::auth::guard::enforce_sandbox_scope(&request, &sandbox_id)?; - drop(request); - - let sandbox = - super::sandbox::fetch_and_authorize_sandbox(state, &principal, &sandbox_id).await?; + let req = request.into_inner(); + let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference( + state, + &principal, + req.sandbox_ref.as_ref(), + MinWorkspaceRole::User, + ) + .await?; + let sandbox_id = sandbox.object_id().to_string(); let workspace = sandbox.object_workspace().to_string(); let sandbox_provider_names = sandbox .spec @@ -3237,42 +3237,35 @@ async fn handle_update_config_inner( ) -> Result, Status> { let req = request.into_inner(); validate_annotations(&req.annotations, "annotations")?; - let workspace = if req.global { - if req.workspace_scope.is_some() { + let sandbox = if req.global { + if req.sandbox_ref.is_some() { return Err(Status::invalid_argument( - "workspace_scope must be omitted when global is true", + "sandbox_ref must be omitted when global is true", )); } require_platform_admin(&state.admin_role, principal)?; - String::new() + None } else { let min_role = if sandbox_caller { MinWorkspaceRole::User } else { MinWorkspaceRole::Admin }; - let workspace = selected_workspace_name(req.workspace_scope.as_ref())?; - authorize_sandbox_workspace( - &state.store, - &state.admin_role, - principal, - workspace, - min_role, + Some( + super::sandbox::resolve_and_authorize_sandbox_reference( + state, + principal, + req.sandbox_ref.as_ref(), + min_role, + ) + .await?, ) - .await?; - super::workspace::resolve_workspace(state.store.as_ref(), workspace) - .await? - .name }; + let workspace = sandbox.as_ref().map_or_else(String::new, |sandbox| { + sandbox.object_workspace().to_string() + }); if sandbox_caller { validate_sandbox_caller_update(&req)?; - resolve_sandbox_by_name_for_principal( - state.store.as_ref(), - &workspace, - principal, - &req.name, - ) - .await?; } let key = req.setting_key.trim(); let has_policy = req.policy.is_some(); @@ -3476,19 +3469,7 @@ async fn handle_update_config_inner( )); } - if req.name.is_empty() { - return Err(Status::invalid_argument( - "name is required for sandbox-scoped updates", - )); - } - - // Resolve sandbox by name. - let sandbox = state - .store - .get_message_by_name::(&workspace, &req.name) - .await - .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? - .ok_or_else(|| Status::not_found("sandbox not found"))?; + let sandbox = sandbox.expect("non-global config update resolves a sandbox"); let sandbox_id = sandbox.object_id().to_string(); let mut response_annotations = sandbox_metadata_annotations(&sandbox); @@ -3925,40 +3906,30 @@ pub(super) async fn handle_get_sandbox_policy_status( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - let workspace = if req.global { - if req.workspace_scope.is_some() { + let sandbox = if req.global { + if req.sandbox_ref.is_some() { return Err(Status::invalid_argument( - "workspace_scope must be omitted when global is true", + "sandbox_ref must be omitted when global is true", )); } require_platform_admin(&state.admin_role, &principal)?; - String::new() + None } else { - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, - &principal, - req.workspace_scope.as_ref(), - MinWorkspaceRole::User, + Some( + super::sandbox::resolve_and_authorize_sandbox_reference( + state, + &principal, + req.sandbox_ref.as_ref(), + MinWorkspaceRole::User, + ) + .await?, ) - .await?; - super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name }; let (policy_id, active_version) = if req.global { (GLOBAL_POLICY_SANDBOX_ID.to_string(), 0_u32) } else { - if req.name.is_empty() { - return Err(Status::invalid_argument("name is required")); - } - let sandbox = state - .store - .get_message_by_name::(&workspace, &req.name) - .await - .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? - .ok_or_else(|| Status::not_found("sandbox not found"))?; + let sandbox = sandbox.as_ref().expect("sandbox query resolved a sandbox"); ( sandbox.object_id().to_string(), sandbox.current_policy_version(), @@ -3998,40 +3969,30 @@ pub(super) async fn handle_list_sandbox_policies( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - let workspace = if req.global { - if req.workspace_scope.is_some() { + let sandbox = if req.global { + if req.sandbox_ref.is_some() { return Err(Status::invalid_argument( - "workspace_scope must be omitted when global is true", + "sandbox_ref must be omitted when global is true", )); } require_platform_admin(&state.admin_role, &principal)?; - String::new() + None } else { - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, - &principal, - req.workspace_scope.as_ref(), - MinWorkspaceRole::User, + Some( + super::sandbox::resolve_and_authorize_sandbox_reference( + state, + &principal, + req.sandbox_ref.as_ref(), + MinWorkspaceRole::User, + ) + .await?, ) - .await?; - super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name }; let policy_id = if req.global { GLOBAL_POLICY_SANDBOX_ID.to_string() } else { - if req.name.is_empty() { - return Err(Status::invalid_argument("name is required")); - } - let sandbox = state - .store - .get_message_by_name::(&workspace, &req.name) - .await - .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? - .ok_or_else(|| Status::not_found("sandbox not found"))?; + let sandbox = sandbox.as_ref().expect("sandbox query resolved a sandbox"); sandbox.object_id().to_string() }; @@ -4140,28 +4101,17 @@ pub(super) async fn handle_get_sandbox_logs( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - if req.sandbox_id.is_empty() { - return Err(Status::invalid_argument("sandbox_id is required")); - } - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::User, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; - let sandbox = - super::sandbox::fetch_and_authorize_sandbox(state, &principal, &req.sandbox_id).await?; - if sandbox.object_workspace() != workspace { - return Err(Status::not_found("sandbox not found")); - } + let sandbox_id = sandbox.object_id(); let lines = if req.lines == 0 { 2000 } else { req.lines }; - let tail = state.tracing_log_bus.tail(&req.sandbox_id, lines as usize); + let tail = state.tracing_log_bus.tail(sandbox_id, lines as usize); let buffer_total = tail.len() as u32; @@ -4689,29 +4639,13 @@ pub(super) async fn handle_get_draft_policy( .cloned() .ok_or_else(|| Status::unauthenticated("missing principal"))?; let req = request.into_inner(); - let workspace_name = selected_workspace_name(req.workspace_scope.as_ref())?; - authorize_sandbox_workspace( - &state.store, - &state.admin_role, + let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference( + state, &principal, - workspace_name, + req.sandbox_ref.as_ref(), MinWorkspaceRole::User, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), workspace_name) - .await? - .name; - if req.name.is_empty() { - return Err(Status::invalid_argument("name is required")); - } - - let sandbox = resolve_sandbox_by_name_for_principal( - state.store.as_ref(), - &workspace, - &principal, - &req.name, - ) - .await?; let sandbox_id = sandbox.object_id().to_string(); let status_filter = if req.status_filter.is_empty() { @@ -4771,32 +4705,20 @@ async fn handle_approve_draft_chunk_inner( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::Admin, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; - if req.name.is_empty() { - return Err(Status::invalid_argument("name is required")); - } + let workspace = sandbox.object_workspace().to_string(); if req.chunk_id.is_empty() { return Err(Status::invalid_argument("chunk_id is required")); } require_no_global_policy(state).await?; - let sandbox = state - .store - .get_message_by_name::(&workspace, &req.name) - .await - .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? - .ok_or_else(|| Status::not_found("sandbox not found"))?; let sandbox_id = sandbox.object_id().to_string(); let chunk = state @@ -4926,30 +4848,18 @@ async fn handle_reject_draft_chunk_inner( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::Admin, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; - if req.name.is_empty() { - return Err(Status::invalid_argument("name is required")); - } + let workspace = sandbox.object_workspace().to_string(); if req.chunk_id.is_empty() { return Err(Status::invalid_argument("chunk_id is required")); } - let sandbox = state - .store - .get_message_by_name::(&workspace, &req.name) - .await - .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? - .ok_or_else(|| Status::not_found("sandbox not found"))?; let sandbox_id = sandbox.object_id().to_string(); let chunk = state @@ -5036,29 +4946,17 @@ async fn handle_approve_all_draft_chunks_inner( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::Admin, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; - if req.name.is_empty() { - return Err(Status::invalid_argument("name is required")); - } + let workspace = sandbox.object_workspace().to_string(); require_no_global_policy(state).await?; - let sandbox = state - .store - .get_message_by_name::(&workspace, &req.name) - .await - .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? - .ok_or_else(|| Status::not_found("sandbox not found"))?; let sandbox_id = sandbox.object_id().to_string(); let pending_chunks = state @@ -5344,20 +5242,14 @@ pub(super) async fn handle_edit_draft_chunk( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::Admin, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; - if req.name.is_empty() { - return Err(Status::invalid_argument("name is required")); - } + let workspace = sandbox.object_workspace().to_string(); if req.chunk_id.is_empty() { return Err(Status::invalid_argument("chunk_id is required")); } @@ -5365,12 +5257,6 @@ pub(super) async fn handle_edit_draft_chunk( .proposed_rule .ok_or_else(|| Status::invalid_argument("proposed_rule is required"))?; - let sandbox = state - .store - .get_message_by_name::(&workspace, &req.name) - .await - .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? - .ok_or_else(|| Status::not_found("sandbox not found"))?; let sandbox_id = sandbox.object_id().to_string(); let chunk = state @@ -5425,30 +5311,18 @@ async fn handle_undo_draft_chunk_inner( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::Admin, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; - if req.name.is_empty() { - return Err(Status::invalid_argument("name is required")); - } + let workspace = sandbox.object_workspace().to_string(); if req.chunk_id.is_empty() { return Err(Status::invalid_argument("chunk_id is required")); } - let sandbox = state - .store - .get_message_by_name::(&workspace, &req.name) - .await - .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? - .ok_or_else(|| Status::not_found("sandbox not found"))?; let sandbox_id = sandbox.object_id().to_string(); let chunk = state @@ -5522,27 +5396,13 @@ pub(super) async fn handle_clear_draft_chunks( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::Admin, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; - if req.name.is_empty() { - return Err(Status::invalid_argument("name is required")); - } - - let sandbox = state - .store - .get_message_by_name::(&workspace, &req.name) - .await - .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? - .ok_or_else(|| Status::not_found("sandbox not found"))?; let sandbox_id = sandbox.object_id().to_string(); let deleted = state @@ -5570,27 +5430,13 @@ pub(super) async fn handle_get_draft_history( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::User, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; - if req.name.is_empty() { - return Err(Status::invalid_argument("name is required")); - } - - let sandbox = state - .store - .get_message_by_name::(&workspace, &req.name) - .await - .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? - .ok_or_else(|| Status::not_found("sandbox not found"))?; let sandbox_id = sandbox.object_id().to_string(); let all_chunks = state @@ -7286,7 +7132,9 @@ mod tests { &state, with_sandbox( Request::new(GetSandboxConfigRequest { - sandbox_id: sandbox_id.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox_id.clone(), + )), }), &sandbox_id, ), @@ -7333,7 +7181,9 @@ mod tests { &state, with_sandbox( Request::new(GetSandboxConfigRequest { - sandbox_id: sandbox_id.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox_id.to_string(), + )), }), sandbox_id, ), @@ -7387,7 +7237,9 @@ mod tests { &state, with_sandbox( Request::new(GetSandboxConfigRequest { - sandbox_id: sandbox_id.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox_id.clone(), + )), }), &sandbox_id, ), @@ -7533,7 +7385,9 @@ mod tests { &state, with_sandbox( Request::new(GetSandboxConfigRequest { - sandbox_id: sandbox_id.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox_id.to_string(), + )), }), sandbox_id, ), @@ -7565,9 +7419,11 @@ mod tests { let detail_error = handle_get_sandbox_policy_status( &state, with_user(Request::new(GetSandboxPolicyStatusRequest { - name: "stored-invalid-history".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "stored-invalid-history".to_string(), + )), version: 2, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() })), ) @@ -7583,9 +7439,11 @@ mod tests { let listed = handle_list_sandbox_policies( &state, with_user(Request::new(ListSandboxPoliciesRequest { - name: "stored-invalid-history".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "stored-invalid-history".to_string(), + )), limit: 10, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() })), ) @@ -7637,7 +7495,9 @@ mod tests { &state, with_sandbox( Request::new(GetSandboxConfigRequest { - sandbox_id: sandbox_id.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox_id.to_string(), + )), }), sandbox_id, ), @@ -7693,7 +7553,9 @@ mod tests { &state, with_sandbox( Request::new(GetSandboxConfigRequest { - sandbox_id: sandbox_id.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox_id.to_string(), + )), }), sandbox_id, ), @@ -7946,9 +7808,11 @@ mod tests { let response = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: sandbox_name, + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + sandbox_name, + )), policy: Some(mcp_policy_with_versions(&["2025-11-25"])), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() })), ) @@ -8053,9 +7917,11 @@ mod tests { let response = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: sandbox_name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + sandbox_name.to_string(), + )), policy: Some(candidate.clone()), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() })), ) @@ -8112,9 +7978,11 @@ mod tests { let response = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: sandbox_name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + sandbox_name.to_string(), + )), policy: Some(candidate.clone()), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() })), ) @@ -8441,9 +8309,11 @@ mod tests { #[test] fn sandbox_caller_update_validation_allows_sandbox_policy_sync() { let req = UpdateConfigRequest { - name: "sandbox-1".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "sandbox-1".to_string(), + )), policy: Some(ProtoSandboxPolicy::default()), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() }; assert!(validate_sandbox_caller_update(&req).is_ok()); @@ -8463,10 +8333,12 @@ mod tests { #[test] fn sandbox_caller_update_validation_rejects_setting_mutation() { let req = UpdateConfigRequest { - name: "sandbox-1".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "sandbox-1".to_string(), + )), setting_key: "inference.model".to_string(), setting_value: Some(SettingValue { value: None }), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() }; let err = validate_sandbox_caller_update(&req).unwrap_err(); @@ -8546,9 +8418,8 @@ mod tests { let error = handle_get_sandbox_logs( &state, with_user(Request::new(GetSandboxLogsRequest { - sandbox_id: "sandbox-b-id".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sandbox-b-id".to_string(), )), ..GetSandboxLogsRequest::default() })), @@ -8643,7 +8514,10 @@ mod tests { &state, authed_request(UpdateConfigRequest { global: true, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + String::new(), + )), ..Default::default() }), ) @@ -8655,7 +8529,10 @@ mod tests { &state, authed_request(GetSandboxPolicyStatusRequest { global: true, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + String::new(), + )), ..Default::default() }), ) @@ -8667,7 +8544,10 @@ mod tests { &state, authed_request(ListSandboxPoliciesRequest { global: true, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + String::new(), + )), ..Default::default() }), ) @@ -8856,7 +8736,9 @@ mod tests { } let req = with_sandbox( Request::new(GetSandboxConfigRequest { - sandbox_id: "sb-b".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sb-b".to_string(), + )), }), "sb-a", ); @@ -8866,6 +8748,25 @@ mod tests { assert_eq!(err.code(), Code::PermissionDenied); } + #[tokio::test] + async fn missing_sandbox_get_sandbox_config_matches_foreign_sandbox_denial() { + let state = test_server_state().await; + let req = with_sandbox( + Request::new(GetSandboxConfigRequest { + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "missing-sandbox".to_string(), + )), + }), + "sb-a", + ); + + let err = handle_get_sandbox_config(&state, req) + .await + .expect_err("missing sandbox must not be distinguishable from a foreign sandbox"); + assert_eq!(err.code(), Code::PermissionDenied); + assert_eq!(err.message(), "sandbox not found or not owned by caller"); + } + #[tokio::test] async fn same_sandbox_get_sandbox_config_allowed() { use openshell_core::proto::{SandboxPhase, SandboxSpec}; @@ -8891,7 +8792,9 @@ mod tests { state.store.put_message(&sandbox).await.unwrap(); let req = with_sandbox( Request::new(GetSandboxConfigRequest { - sandbox_id: "sb-self".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sb-self".to_string(), + )), }), "sb-self", ); @@ -8965,11 +8868,11 @@ mod tests { } let req = with_sandbox( Request::new(GetDraftPolicyRequest { - name: "sandbox-b".to_string(), - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "sandbox-b".to_string(), )), + status_filter: String::new(), }), "sb-a", ); @@ -8980,13 +8883,15 @@ mod tests { } #[tokio::test] - async fn sandbox_update_config_missing_name_returns_permission_denied() { + async fn sandbox_update_config_missing_name_matches_foreign_sandbox_denial() { let state = test_server_state().await; let req = with_sandbox( Request::new(UpdateConfigRequest { - name: "missing-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "missing-sandbox".to_string(), + )), policy: Some(ProtoSandboxPolicy::default()), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() }), "sb-a", @@ -9016,15 +8921,15 @@ mod tests { } #[tokio::test] - async fn sandbox_get_draft_policy_missing_name_returns_permission_denied() { + async fn sandbox_get_draft_policy_missing_name_matches_foreign_sandbox_denial() { let state = test_server_state().await; let req = with_sandbox( Request::new(GetDraftPolicyRequest { - name: "missing-sandbox".to_string(), - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "missing-sandbox".to_string(), )), + status_filter: String::new(), }), "sb-a", ); @@ -9060,7 +8965,9 @@ mod tests { sandbox.set_phase(SandboxPhase::Provisioning as i32); state.store.put_message(&sandbox).await.unwrap(); let req = with_user(Request::new(GetSandboxConfigRequest { - sandbox_id: "sb-x".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sb-x".to_string(), + )), })); handle_get_sandbox_config(&state, req) .await @@ -9280,7 +9187,9 @@ mod tests { handle_get_sandbox_config( state, with_user(Request::new(GetSandboxConfigRequest { - sandbox_id: sandbox_id.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox_id.to_string(), + )), })), ) .await @@ -9394,7 +9303,9 @@ mod tests { let first = handle_get_sandbox_config( &state, with_user(Request::new(GetSandboxConfigRequest { - sandbox_id: "sb-snapshot-consistency".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sb-snapshot-consistency".to_string(), + )), })), ) .await @@ -9416,7 +9327,9 @@ mod tests { let second = handle_get_sandbox_config( &state, with_user(Request::new(GetSandboxConfigRequest { - sandbox_id: "sb-snapshot-consistency".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sb-snapshot-consistency".to_string(), + )), })), ) .await @@ -10019,7 +9932,9 @@ mod tests { let response = handle_get_sandbox_config( &state, with_user(Request::new(GetSandboxConfigRequest { - sandbox_id: "sb-mcp-default-composed".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sb-mcp-default-composed".to_string(), + )), })), ) .await @@ -10082,9 +9997,9 @@ mod tests { let error = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "ambiguous-update".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "ambiguous-update".to_string(), )), policy: Some(test_ambiguous_policy()), ..Default::default() @@ -10121,9 +10036,9 @@ mod tests { let error = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "unattached-binding".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "unattached-binding".to_string(), )), policy: Some(test_policy_with_credential_binding( "cloud", @@ -10168,9 +10083,9 @@ mod tests { let error = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "double-binding".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "double-binding".to_string(), )), policy: Some(test_policy_with_credential_binding( "cloud", @@ -10240,9 +10155,9 @@ mod tests { let l4_error = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "endpointless-gating".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "endpointless-gating".to_string(), )), policy: Some(l4.clone()), ..Default::default() @@ -10265,9 +10180,9 @@ mod tests { let tls_error = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "endpointless-gating".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "endpointless-gating".to_string(), )), policy: Some(tls_skip.clone()), ..Default::default() @@ -10281,9 +10196,9 @@ mod tests { let merge_l4_error = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "endpointless-gating".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "endpointless-gating".to_string(), )), merge_operations: vec![add_bound_rule(&l4)], ..Default::default() @@ -10297,9 +10212,9 @@ mod tests { let merge_tls_error = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "endpointless-gating".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "endpointless-gating".to_string(), )), merge_operations: vec![add_bound_rule(&tls_skip)], ..Default::default() @@ -10330,9 +10245,9 @@ mod tests { handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "endpointless-gating".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "endpointless-gating".to_string(), )), merge_operations: vec![add_bound_rule(&opted_in)], ..Default::default() @@ -10365,9 +10280,9 @@ mod tests { let error = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "signing-no-source".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "signing-no-source".to_string(), )), policy: Some(test_sigv4_policy("s3.amazonaws.com", None)), ..Default::default() @@ -10413,9 +10328,9 @@ mod tests { let error = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "signing-unbound-aws".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "signing-unbound-aws".to_string(), )), policy: Some(test_sigv4_policy("s3.amazonaws.com", None)), ..Default::default() @@ -10452,9 +10367,9 @@ mod tests { handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "signing-bound-aws".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "signing-bound-aws".to_string(), )), policy: Some(test_sigv4_policy("s3.amazonaws.com", Some("aws-prod"))), ..Default::default() @@ -10498,9 +10413,9 @@ mod tests { handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "signing-profile-endpoint".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "signing-profile-endpoint".to_string(), )), policy: Some(policy), ..Default::default() @@ -10530,9 +10445,9 @@ mod tests { let error = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "signing-profile-mismatch".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "signing-profile-mismatch".to_string(), )), policy: Some(test_sigv4_policy("api.example.com", None)), ..Default::default() @@ -10640,12 +10555,12 @@ mod tests { let error = super::super::sandbox::handle_attach_sandbox_provider( &state, authed_request(openshell_core::proto::AttachSandboxProviderRequest { - sandbox_name: "provider-ambiguity".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "provider-ambiguity".to_string(), + )), provider_name: "candidate-provider".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -10731,7 +10646,9 @@ mod tests { let error = handle_get_sandbox_config( &state, with_user(Request::new(GetSandboxConfigRequest { - sandbox_id: "sb-invalid-composed-policy".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sb-invalid-composed-policy".to_string(), + )), })), ) .await @@ -11223,7 +11140,9 @@ mod tests { let config = handle_get_sandbox_config( &state, with_user(Request::new(GetSandboxConfigRequest { - sandbox_id: "sb-policy-binding".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sb-policy-binding".to_string(), + )), })), ) .await @@ -11272,9 +11191,9 @@ mod tests { handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "policy-binding".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "policy-binding".to_string(), )), policy: Some(next_policy.clone()), ..Default::default() @@ -11285,7 +11204,9 @@ mod tests { let next_config = handle_get_sandbox_config( &state, with_user(Request::new(GetSandboxConfigRequest { - sandbox_id: "sb-policy-binding".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sb-policy-binding".to_string(), + )), })), ) .await @@ -11329,9 +11250,9 @@ mod tests { handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "policy-binding".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "policy-binding".to_string(), )), policy: Some(unbound_policy), ..Default::default() @@ -11342,7 +11263,9 @@ mod tests { let unbound_config = handle_get_sandbox_config( &state, with_user(Request::new(GetSandboxConfigRequest { - sandbox_id: "sb-policy-binding".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sb-policy-binding".to_string(), + )), })), ) .await @@ -12119,12 +12042,12 @@ mod tests { handle_attach_sandbox_provider( &state, with_user(Request::new(AttachSandboxProviderRequest { - sandbox_name: "attach-lifecycle".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "attach-lifecycle".to_string(), + )), provider_name: "work-github".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), })), ) .await @@ -12159,12 +12082,12 @@ mod tests { handle_detach_sandbox_provider( &state, authed_request(DetachSandboxProviderRequest { - sandbox_name: "attach-lifecycle".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "attach-lifecycle".to_string(), + )), provider_name: "work-github".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -12293,12 +12216,12 @@ mod tests { handle_attach_sandbox_provider( &state, with_user(Request::new(AttachSandboxProviderRequest { - sandbox_name: "attach-lifecycle".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "attach-lifecycle".to_string(), + )), provider_name: "work-custom".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), })), ) .await @@ -12336,12 +12259,12 @@ mod tests { handle_detach_sandbox_provider( &state, authed_request(DetachSandboxProviderRequest { - sandbox_name: "attach-lifecycle".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "attach-lifecycle".to_string(), + )), provider_name: "work-custom".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -12453,7 +12376,9 @@ mod tests { let response = handle_get_sandbox_config( &state, with_user(Request::new(GetSandboxConfigRequest { - sandbox_id: "sb-global-profile".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sb-global-profile".to_string(), + )), })), ) .await @@ -12647,9 +12572,9 @@ mod tests { let approved = handle_approve_all_draft_chunks( &state, with_user(Request::new(ApproveAllDraftChunksRequest { - name: sandbox_name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.to_string(), )), approvals: chunks .iter() @@ -12776,9 +12701,9 @@ mod tests { let approved = handle_approve_all_draft_chunks( &state, with_user(Request::new(ApproveAllDraftChunksRequest { - name: sandbox_name.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.to_string(), )), approvals: chunks .iter() @@ -12979,11 +12904,11 @@ mod tests { let skipped = handle_approve_all_draft_chunks( &state, with_user(Request::new(ApproveAllDraftChunksRequest { - name: sandbox_name.to_string(), - include_security_flagged: false, - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.to_string(), )), + include_security_flagged: false, approvals: vec![openshell_core::proto::DraftChunkApproval { chunk_id: chunk_id.clone(), review_token: chunk.review_token.clone(), @@ -13009,11 +12934,11 @@ mod tests { let approved = handle_approve_all_draft_chunks( &state, with_user(Request::new(ApproveAllDraftChunksRequest { - name: sandbox_name.to_string(), - include_security_flagged: true, - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.to_string(), )), + include_security_flagged: true, approvals: vec![openshell_core::proto::DraftChunkApproval { chunk_id: chunk_id.clone(), review_token: chunk.review_token.clone(), @@ -13088,12 +13013,12 @@ mod tests { handle_edit_draft_chunk( &state, with_user(Request::new(EditDraftChunkRequest { - name: sandbox_name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + sandbox_name.to_string(), + )), chunk_id: chunk_id.clone(), proposed_rule: Some(private_rule), - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), })), ) .await @@ -13110,11 +13035,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name.to_string(), - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.to_string(), )), + status_filter: String::new(), })), ) .await @@ -13129,11 +13054,11 @@ mod tests { let skipped = handle_approve_all_draft_chunks( &state, with_user(Request::new(ApproveAllDraftChunksRequest { - name: sandbox_name.to_string(), - include_security_flagged: false, - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.to_string(), )), + include_security_flagged: false, ..Default::default() })), ) @@ -13197,11 +13122,11 @@ mod tests { let skipped = handle_approve_all_draft_chunks( &state, with_user(Request::new(ApproveAllDraftChunksRequest { - name: sandbox_name.to_string(), - include_security_flagged: false, - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.to_string(), )), + include_security_flagged: false, ..Default::default() })), ) @@ -13269,11 +13194,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name.to_string(), - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.to_string(), )), + status_filter: String::new(), })), ) .await @@ -13384,12 +13309,12 @@ mod tests { handle_edit_draft_chunk( &state, with_user(Request::new(EditDraftChunkRequest { - name: sandbox_name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + sandbox_name.to_string(), + )), chunk_id: chunk_id.clone(), proposed_rule: Some(finding_rule), - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), })), ) .await @@ -13520,11 +13445,11 @@ mod tests { let draft_policy = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name.clone(), - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), + status_filter: String::new(), })), ) .await @@ -13542,11 +13467,11 @@ mod tests { let approve = handle_approve_draft_chunk( &state, authed_request(ApproveDraftChunkRequest { - name: sandbox_name.clone(), - chunk_id: chunk_id.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), + chunk_id: chunk_id.clone(), review_token, }), ) @@ -13559,9 +13484,9 @@ mod tests { let history_after_approve = handle_get_draft_history( &state, authed_request(GetDraftHistoryRequest { - name: sandbox_name.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), }), ) @@ -13576,13 +13501,13 @@ mod tests { let policies_after_approve = handle_list_sandbox_policies( &state, authed_request(ListSandboxPoliciesRequest { - name: sandbox_name.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + sandbox_name.clone(), + )), limit: 10, offset: 0, global: false, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -13594,11 +13519,11 @@ mod tests { let undo = handle_undo_draft_chunk( &state, authed_request(UndoDraftChunkRequest { - name: sandbox_name.clone(), - chunk_id: chunk_id.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), + chunk_id: chunk_id.clone(), }), ) .await @@ -13610,11 +13535,11 @@ mod tests { let draft_policy_after_undo = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name.clone(), - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), + status_filter: String::new(), })), ) .await @@ -13626,9 +13551,9 @@ mod tests { let history_after_undo = handle_get_draft_history( &state, authed_request(GetDraftHistoryRequest { - name: sandbox_name.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), }), ) @@ -13641,13 +13566,13 @@ mod tests { let policies_after_undo = handle_list_sandbox_policies( &state, authed_request(ListSandboxPoliciesRequest { - name: sandbox_name.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + sandbox_name.clone(), + )), limit: 10, offset: 0, global: false, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -13660,9 +13585,9 @@ mod tests { let cleared = handle_clear_draft_chunks( &state, authed_request(ClearDraftChunksRequest { - name: sandbox_name.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), }), ) @@ -13674,11 +13599,11 @@ mod tests { let draft_policy_after_clear = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name.clone(), - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), + status_filter: String::new(), })), ) .await @@ -13689,9 +13614,9 @@ mod tests { let history_after_clear = handle_get_draft_history( &state, authed_request(GetDraftHistoryRequest { - name: sandbox_name, - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), }), ) @@ -13766,12 +13691,12 @@ mod tests { handle_reject_draft_chunk( &state, authed_request(RejectDraftChunkRequest { - name: sandbox_name.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + sandbox_name.clone(), + )), chunk_id: chunk_id.clone(), reason: guidance.to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -13780,11 +13705,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -13889,11 +13814,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -14004,11 +13929,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name.clone(), - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), + status_filter: String::new(), })), ) .await @@ -14082,11 +14007,11 @@ mod tests { let draft_after = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -14214,11 +14139,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -14321,9 +14246,9 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), ..Default::default() })), @@ -14446,9 +14371,9 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), ..Default::default() })), @@ -14578,11 +14503,11 @@ mod tests { let error = handle_approve_draft_chunk( &state, with_user(Request::new(ApproveDraftChunkRequest { - name: sandbox_name, - chunk_id: chunk_id.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + chunk_id: chunk_id.clone(), review_token: before.review_token.clone(), })), ) @@ -14764,11 +14689,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -14870,11 +14795,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -14969,11 +14894,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -15059,11 +14984,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -15153,11 +15078,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -15250,11 +15175,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -15426,11 +15351,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name.to_string(), - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.to_string(), )), + status_filter: String::new(), })), ) .await @@ -15501,11 +15426,11 @@ mod tests { let err = handle_approve_draft_chunk( &state, with_user(Request::new(ApproveDraftChunkRequest { - name: sandbox_name.to_string(), - chunk_id: chunk.id.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.to_string(), )), + chunk_id: chunk.id.clone(), ..Default::default() })), ) @@ -15614,11 +15539,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -15716,11 +15641,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -15807,11 +15732,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -15970,11 +15895,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name.clone(), - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), + status_filter: String::new(), })), ) .await @@ -16002,11 +15927,11 @@ mod tests { handle_approve_draft_chunk( &state, authed_request(ApproveDraftChunkRequest { - name: sandbox_name, - chunk_id, - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + chunk_id, review_token: chunk.review_token.clone(), }), ) @@ -16192,11 +16117,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -16345,11 +16270,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name.clone(), - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), + status_filter: String::new(), })), ) .await @@ -16368,12 +16293,12 @@ mod tests { handle_reject_draft_chunk( &state, authed_request(RejectDraftChunkRequest { - name: sandbox_name, + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + sandbox_name, + )), chunk_id: second.accepted_chunk_ids[0].clone(), reason: "redraft test".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -16455,11 +16380,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -16573,11 +16498,11 @@ mod tests { let after_first = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name.clone(), - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), + status_filter: String::new(), })), ) .await @@ -16596,11 +16521,11 @@ mod tests { let after_second = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name.clone(), - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), + status_filter: String::new(), })), ) .await @@ -16840,12 +16765,12 @@ mod tests { handle_reject_draft_chunk( &state, authed_request(RejectDraftChunkRequest { - name: sandbox_name.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + sandbox_name.clone(), + )), chunk_id: chunk_id.clone(), reason: "scope too broad".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -16854,11 +16779,11 @@ mod tests { handle_approve_draft_chunk( &state, authed_request(ApproveDraftChunkRequest { - name: sandbox_name.clone(), - chunk_id: chunk_id.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), + chunk_id: chunk_id.clone(), review_token, }), ) @@ -16868,11 +16793,11 @@ mod tests { handle_undo_draft_chunk( &state, authed_request(UndoDraftChunkRequest { - name: sandbox_name.clone(), - chunk_id: chunk_id.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name.clone(), )), + chunk_id: chunk_id.clone(), }), ) .await @@ -16881,11 +16806,11 @@ mod tests { let draft = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_name, - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_name, )), + status_filter: String::new(), })), ) .await @@ -16995,11 +16920,11 @@ mod tests { let draft_policy = handle_get_draft_policy( &state, with_user(Request::new(GetDraftPolicyRequest { - name: sandbox_a.object_name().to_string(), - status_filter: String::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_a.object_name().to_string(), )), + status_filter: String::new(), })), ) .await @@ -17012,11 +16937,11 @@ mod tests { let approve_err = handle_approve_draft_chunk( &state, authed_request(ApproveDraftChunkRequest { - name: other_name.clone(), - chunk_id: chunk_id.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + other_name.clone(), )), + chunk_id: chunk_id.clone(), review_token: String::new(), }), ) @@ -17027,12 +16952,12 @@ mod tests { let reject_err = handle_reject_draft_chunk( &state, authed_request(RejectDraftChunkRequest { - name: other_name.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + other_name.clone(), + )), chunk_id: chunk_id.clone(), reason: "wrong sandbox".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -17042,12 +16967,12 @@ mod tests { let edit_err = handle_edit_draft_chunk( &state, authed_request(EditDraftChunkRequest { - name: other_name.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + other_name.clone(), + )), chunk_id: chunk_id.clone(), proposed_rule: Some(proposed_rule.clone()), - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -17057,11 +16982,11 @@ mod tests { handle_approve_draft_chunk( &state, authed_request(ApproveDraftChunkRequest { - name: sandbox_a.object_name().to_string(), - chunk_id: chunk_id.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + sandbox_a.object_name().to_string(), )), + chunk_id: chunk_id.clone(), review_token, }), ) @@ -17071,11 +16996,11 @@ mod tests { let undo_err = handle_undo_draft_chunk( &state, authed_request(UndoDraftChunkRequest { - name: other_name, - chunk_id, - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + other_name, )), + chunk_id, }), ) .await @@ -19013,7 +18938,9 @@ mod tests { let alpha_req = with_sandbox( Request::new(GetSandboxConfigRequest { - sandbox_id: "sb-alpha".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sb-alpha".to_string(), + )), }), "sb-alpha", ); @@ -19025,7 +18952,9 @@ mod tests { let beta_req = with_sandbox( Request::new(GetSandboxConfigRequest { - sandbox_id: "sb-beta".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sb-beta".to_string(), + )), }), "sb-beta", ); @@ -19174,7 +19103,10 @@ mod tests { let response = handle_update_config( &state, authed_request(UpdateConfigRequest { - name: "test-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "test-sandbox".to_string(), + )), policy: Some(new_policy), setting_key: String::new(), setting_value: None, @@ -19183,9 +19115,6 @@ mod tests { merge_operations: vec![], expected_resource_version: current_version, annotations: HashMap::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -19272,7 +19201,10 @@ mod tests { let response = handle_update_config( &state, authed_request(UpdateConfigRequest { - name: "annotated-backfill".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "annotated-backfill".to_string(), + )), policy: Some(ProtoSandboxPolicy::default()), setting_key: String::new(), setting_value: None, @@ -19281,9 +19213,6 @@ mod tests { merge_operations: vec![], expected_resource_version: current_version, annotations: annotations.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -19350,13 +19279,15 @@ mod tests { let response = handle_update_config( &state, authed_request(UpdateConfigRequest { - name: "same-hash".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "same-hash".to_string(), + )), policy: Some(policy), annotations: HashMap::from([( "openshell.nvidia.com/policy-signature".to_string(), "same-hash-signature".to_string(), )]), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() }), ) @@ -19437,10 +19368,12 @@ mod tests { let first = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "idempotent-provenance".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "idempotent-provenance".to_string(), + )), policy: Some(policy.clone()), annotations: annotations.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() })), ) @@ -19450,10 +19383,12 @@ mod tests { let second = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "idempotent-provenance".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "idempotent-provenance".to_string(), + )), policy: Some(policy), annotations: annotations.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() })), ) @@ -19505,9 +19440,11 @@ mod tests { let response = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "preserve-full".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "preserve-full".to_string(), + )), policy: Some(updated), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() })), ) @@ -19554,7 +19491,10 @@ mod tests { let response = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "preserve-merge".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "preserve-merge", + )), merge_operations: vec![PolicyMergeOperation { operation: Some(policy_merge_operation::Operation::AddRule( openshell_core::proto::AddNetworkRule { @@ -19571,7 +19511,6 @@ mod tests { }, )), }], - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() })), ) @@ -19626,7 +19565,10 @@ mod tests { let response = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "merge-provenance".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "merge-provenance", + )), merge_operations: vec![PolicyMergeOperation { operation: Some(policy_merge_operation::Operation::AddRule( openshell_core::proto::AddNetworkRule { @@ -19644,7 +19586,6 @@ mod tests { )), }], annotations: provenance.clone(), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() })), ) @@ -19703,10 +19644,12 @@ mod tests { let response = handle_update_config( &state, authed_request(UpdateConfigRequest { - name: "preserve-backfill".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "preserve-backfill".to_string(), + )), policy: Some(ProtoSandboxPolicy::default()), expected_resource_version: current_version, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() }), ) @@ -19778,12 +19721,12 @@ mod tests { let error = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: sandbox_name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + sandbox_name.to_string(), + )), policy: Some(unsafe_replacement), expected_resource_version: current_version, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), ..Default::default() })), ) @@ -19856,12 +19799,12 @@ mod tests { let error = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: sandbox_name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + sandbox_name.to_string(), + )), policy: Some(mcp_policy_with_versions(versions)), expected_resource_version: current_version, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), ..Default::default() })), ) @@ -19940,12 +19883,12 @@ mod tests { let response = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: sandbox_name.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + sandbox_name.clone(), + )), policy: Some(policy), expected_resource_version: current_version, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), ..Default::default() })), ) @@ -20027,16 +19970,16 @@ mod tests { handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: sandbox_name.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + sandbox_name.to_string(), + )), policy: Some(mcp_policy_with_versions(&[ "2025-11-25", "2025-06-18", "2025-03-26", ])), expected_resource_version: current_version, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), ..Default::default() })), ) @@ -20108,10 +20051,12 @@ mod tests { let err = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "invalid-annotation".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "invalid-annotation".to_string(), + )), policy: Some(ProtoSandboxPolicy::default()), annotations: HashMap::from([("bad key".to_string(), "value".to_string())]), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() })), ) @@ -20139,12 +20084,14 @@ mod tests { let err = handle_update_config( &state, with_user(Request::new(UpdateConfigRequest { - name: "user-reserved-key".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "user-reserved-key".to_string(), + )), policy: Some(test_policy_with_rule( "_provider_work_github", "api.github.com", )), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() })), ) @@ -20208,10 +20155,12 @@ mod tests { &state, with_sandbox( Request::new(UpdateConfigRequest { - name: "sync-strip".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "sync-strip".to_string(), + )), policy: Some(synced_policy), expected_resource_version: current_version, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() }), "sb-sync-strip", @@ -20304,7 +20253,10 @@ mod tests { let err = handle_update_config( &state, authed_request(UpdateConfigRequest { - name: "test-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "test-sandbox".to_string(), + )), policy: Some(new_policy), setting_key: String::new(), setting_value: None, @@ -20313,9 +20265,6 @@ mod tests { merge_operations: vec![], expected_resource_version: 99, // stale version annotations: HashMap::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -20405,7 +20354,10 @@ mod tests { handle_update_config( &state_clone, authed_request(UpdateConfigRequest { - name: "test-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "test-sandbox".to_string(), + )), policy: Some(new_policy), setting_key: String::new(), setting_value: None, @@ -20414,9 +20366,6 @@ mod tests { merge_operations: vec![], expected_resource_version: initial_version, annotations: HashMap::new(), - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -20477,7 +20426,7 @@ mod tests { /// when targeting a workspace that does not exist. Returning `NOT_FOUND` /// would create a CWE-203 workspace-name oracle. #[tokio::test] - async fn non_member_gets_permission_denied_not_workspace_oracle() { + async fn non_member_gets_gets_not_found_without_sandbox_oracle() { let mut state = test_server_state().await; Arc::get_mut(&mut state).unwrap().admin_role = "openshell-admin".to_string(); @@ -20498,7 +20447,10 @@ mod tests { let err = handle_get_sandbox_policy_status( &state, non_member_request(GetSandboxPolicyStatusRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), ..Default::default() }), ) @@ -20506,15 +20458,18 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_get_sandbox_policy_status should return PermissionDenied, got {:?}", + Code::NotFound, + "handle_get_sandbox_policy_status should return NotFound, got {:?}", err.code() ); let err = handle_list_sandbox_policies( &state, non_member_request(ListSandboxPoliciesRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), ..Default::default() }), ) @@ -20522,15 +20477,18 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_list_sandbox_policies should return PermissionDenied, got {:?}", + Code::NotFound, + "handle_list_sandbox_policies should return NotFound, got {:?}", err.code() ); let err = handle_update_config( &state, non_member_request(UpdateConfigRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), ..Default::default() }), ) @@ -20538,15 +20496,18 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_update_config should return PermissionDenied, got {:?}", + Code::NotFound, + "handle_update_config should return NotFound, got {:?}", err.code() ); let err = handle_get_draft_policy( &state, non_member_request(GetDraftPolicyRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), ..Default::default() }), ) @@ -20554,15 +20515,18 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_get_draft_policy should return PermissionDenied, got {:?}", + Code::NotFound, + "handle_get_draft_policy should return NotFound, got {:?}", err.code() ); let err = handle_approve_draft_chunk( &state, non_member_request(ApproveDraftChunkRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), ..Default::default() }), ) @@ -20570,15 +20534,18 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_approve_draft_chunk should return PermissionDenied, got {:?}", + Code::NotFound, + "handle_approve_draft_chunk should return NotFound, got {:?}", err.code() ); let err = handle_reject_draft_chunk( &state, non_member_request(RejectDraftChunkRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), ..Default::default() }), ) @@ -20586,15 +20553,18 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_reject_draft_chunk should return PermissionDenied, got {:?}", + Code::NotFound, + "handle_reject_draft_chunk should return NotFound, got {:?}", err.code() ); let err = handle_approve_all_draft_chunks( &state, non_member_request(ApproveAllDraftChunksRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), ..Default::default() }), ) @@ -20602,15 +20572,18 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_approve_all_draft_chunks should return PermissionDenied, got {:?}", + Code::NotFound, + "handle_approve_all_draft_chunks should return NotFound, got {:?}", err.code() ); let err = handle_edit_draft_chunk( &state, non_member_request(EditDraftChunkRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), ..Default::default() }), ) @@ -20618,15 +20591,18 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_edit_draft_chunk should return PermissionDenied, got {:?}", + Code::NotFound, + "handle_edit_draft_chunk should return NotFound, got {:?}", err.code() ); let err = handle_undo_draft_chunk( &state, non_member_request(UndoDraftChunkRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), ..Default::default() }), ) @@ -20634,40 +20610,44 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_undo_draft_chunk should return PermissionDenied, got {:?}", + Code::NotFound, + "handle_undo_draft_chunk should return NotFound, got {:?}", err.code() ); let err = handle_clear_draft_chunks( &state, non_member_request(ClearDraftChunksRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), - ..Default::default() + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), }), ) .await .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_clear_draft_chunks should return PermissionDenied, got {:?}", + Code::NotFound, + "handle_clear_draft_chunks should return NotFound, got {:?}", err.code() ); let err = handle_get_draft_history( &state, non_member_request(GetDraftHistoryRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), - ..Default::default() + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), }), ) .await .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_get_draft_history should return PermissionDenied, got {:?}", + Code::NotFound, + "handle_get_draft_history should return NotFound, got {:?}", err.code() ); } @@ -20706,7 +20686,9 @@ mod tests { let err = handle_get_sandbox_config( &state, non_member_request(GetSandboxConfigRequest { - sandbox_id: "sandbox-other".into(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sandbox-other", + )), }), ) .await @@ -20721,8 +20703,9 @@ mod tests { let err = handle_get_sandbox_logs( &state, non_member_request(GetSandboxLogsRequest { - sandbox_id: "sandbox-other".into(), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sandbox-other", + )), ..Default::default() }), ) @@ -20730,8 +20713,8 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_get_sandbox_logs must authorize the selected workspace before lookup" + Code::NotFound, + "handle_get_sandbox_logs must hide unauthorized sandbox existence" ); } diff --git a/crates/openshell-server/src/grpc/provider.rs b/crates/openshell-server/src/grpc/provider.rs index 1790180ea..e5a774994 100644 --- a/crates/openshell-server/src/grpc/provider.rs +++ b/crates/openshell-server/src/grpc/provider.rs @@ -6566,12 +6566,12 @@ mod tests { let attached = super::super::sandbox::handle_attach_sandbox_provider( &state, authed_request(AttachSandboxProviderRequest { - sandbox_name: "sandbox-custom".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "sandbox-custom".to_string(), + )), provider_name: "custom-provider".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await diff --git a/crates/openshell-server/src/grpc/sandbox.rs b/crates/openshell-server/src/grpc/sandbox.rs index 17ae70d2f..5328b7812 100644 --- a/crates/openshell-server/src/grpc/sandbox.rs +++ b/crates/openshell-server/src/grpc/sandbox.rs @@ -31,7 +31,7 @@ use openshell_core::proto::{ SandboxSpec, SandboxStreamEvent, SandboxTemplateResponse, SandboxWorkloadTemplate, SandboxWorkloadTemplateProvenance, SshRelayTarget, StartSandboxRequest, StopSandboxRequest, TcpForwardFrame, TcpForwardInit, TcpRelayTarget, WatchSandboxRequest, relay_open, - tcp_forward_init, + sandbox_reference, tcp_forward_init, }; use openshell_core::proto::{ BeginRootfsTarStagingRequest, BeginRootfsTarStagingResponse, Sandbox, SandboxPhase, @@ -121,33 +121,78 @@ impl Drop for WatchSandboxStream { } } -/// Fetch a sandbox by ID and authorize the caller in one step, returning -/// `NOT_FOUND` for both missing and unauthorized sandboxes so that callers -/// cannot distinguish the two cases (CWE-203). -pub(super) async fn fetch_and_authorize_sandbox( +/// Resolve a canonical public sandbox reference and authorize its persisted +/// workspace. Missing and unauthorized objects deliberately share one response +/// so neither reference form can be used as an existence oracle. +pub(super) async fn resolve_and_authorize_sandbox_reference( state: &Arc, principal: &crate::auth::principal::Principal, - sandbox_id: &str, + sandbox_ref: Option<&openshell_core::proto::SandboxReference>, + min_role: MinWorkspaceRole, ) -> Result { - let sandbox = state - .store - .get_message::(sandbox_id) - .await - .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? - .ok_or_else(|| Status::not_found("sandbox not found"))?; + let sandbox_ref = + sandbox_ref.ok_or_else(|| Status::invalid_argument("sandbox_ref is required"))?; + let sandbox = match sandbox_ref.identifier.as_ref() { + Some(sandbox_reference::Identifier::Id(id)) => { + if id.is_empty() { + return Err(Status::invalid_argument("sandbox_ref.id is required")); + } + if sandbox_ref.workspace_scope.is_some() { + return Err(Status::invalid_argument( + "sandbox_ref.workspace_scope must be omitted for an ID reference", + )); + } + state.store.get_message::(id).await + } + Some(sandbox_reference::Identifier::Name(name)) => { + if name.is_empty() { + return Err(Status::invalid_argument("sandbox_ref.name is required")); + } + let workspace = crate::auth::workspace_authz::selected_workspace_name( + sandbox_ref.workspace_scope.as_ref(), + )?; + state + .store + .get_message_by_name::(workspace, name) + .await + } + None => { + return Err(Status::invalid_argument( + "sandbox_ref identifier is required", + )); + } + } + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))?; + let sandbox = match sandbox { + Some(sandbox) => sandbox, + None if matches!(principal, crate::auth::principal::Principal::Sandbox(_)) => { + return Err(Status::permission_denied( + "sandbox not found or not owned by caller", + )); + } + None => return Err(Status::not_found("sandbox not found")), + }; + authorize_sandbox_workspace( &state.store, &state.admin_role, principal, sandbox.object_workspace(), - MinWorkspaceRole::User, + min_role, ) .await - .map_err(|e| { - if e.code() == tonic::Code::PermissionDenied { + .map_err(|error| { + if error.code() == tonic::Code::PermissionDenied { Status::not_found("sandbox not found") } else { - e + error + } + })?; + crate::auth::guard::ensure_sandbox_scope(principal, sandbox.object_id()).map_err(|error| { + if error.code() == tonic::Code::PermissionDenied { + Status::permission_denied("sandbox not found or not owned by caller") + } else { + error } })?; Ok(sandbox) @@ -663,28 +708,13 @@ pub(super) async fn handle_get_sandbox( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - if req.name.is_empty() { - return Err(Status::invalid_argument("name is required")); - } - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::User, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; - - let sandbox = state - .store - .get_message_by_name::(&workspace, &req.name) - .await - .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))?; - - let sandbox = sandbox.ok_or_else(|| Status::not_found("sandbox not found"))?; Ok(Response::new(SandboxResponse { sandbox: Some(sandbox), })) @@ -1037,18 +1067,14 @@ pub(super) async fn handle_list_sandbox_providers( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::User, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; - let sandbox = sandbox_by_name(state, &workspace, &req.sandbox_name).await?; + let workspace = sandbox.object_workspace().to_string(); let providers = providers_for_sandbox(state, &sandbox, &workspace).await?; Ok(Response::new(ListSandboxProvidersResponse { providers })) } @@ -1059,17 +1085,17 @@ pub(super) async fn handle_attach_sandbox_provider( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let request = request.into_inner(); - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = resolve_and_authorize_sandbox_reference( + state, &principal, - request.workspace_scope.as_ref(), + request.sandbox_ref.as_ref(), MinWorkspaceRole::User, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .ensure_active()?; + let workspace = + super::workspace::resolve_workspace(state.store.as_ref(), sandbox.object_workspace()) + .await? + .ensure_active()?; if request.provider_name.is_empty() { return Err(Status::invalid_argument("provider_name is required")); } @@ -1098,7 +1124,7 @@ pub(super) async fn handle_attach_sandbox_provider( })?; let _sandbox_sync_guard = state.compute.sandbox_sync_guard().await; - let sandbox = sandbox_by_name(state, &workspace, &request.sandbox_name).await?; + let sandbox_name = sandbox.object_name().to_string(); let sandbox_id = sandbox .metadata .as_ref() @@ -1133,7 +1159,7 @@ pub(super) async fn handle_attach_sandbox_provider( { candidate_spec.providers.push(request.provider_name.clone()); } - validate_sandbox_spec(&request.sandbox_name, &candidate_spec)?; + validate_sandbox_spec(&sandbox_name, &candidate_spec)?; let provider_profile_catalog = state .provider_profile_sources .snapshot_catalog(state.store.as_ref(), &workspace) @@ -1190,7 +1216,7 @@ pub(super) async fn handle_attach_sandbox_provider( let attached = attached.load(Ordering::Relaxed); info!( - sandbox_name = %request.sandbox_name, + sandbox_name = %sandbox_name, provider_name = %request.provider_name, attached, "AttachSandboxProvider request completed successfully" @@ -1208,17 +1234,14 @@ pub(super) async fn handle_detach_sandbox_provider( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let request = request.into_inner(); - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = resolve_and_authorize_sandbox_reference( + state, &principal, - request.workspace_scope.as_ref(), + request.sandbox_ref.as_ref(), MinWorkspaceRole::User, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; + let workspace = sandbox.object_workspace().to_string(); if request.provider_name.is_empty() { return Err(Status::invalid_argument("provider_name is required")); } @@ -1233,7 +1256,7 @@ pub(super) async fn handle_detach_sandbox_provider( } let _sandbox_sync_guard = state.compute.sandbox_sync_guard().await; - let sandbox = sandbox_by_name(state, &workspace, &request.sandbox_name).await?; + let sandbox_name = sandbox.object_name().to_string(); let sandbox_id = sandbox .metadata .as_ref() @@ -1289,7 +1312,7 @@ pub(super) async fn handle_detach_sandbox_provider( let detached = detached.load(Ordering::Relaxed); info!( - sandbox_name = %request.sandbox_name, + sandbox_name = %sandbox_name, provider_name = %request.provider_name, detached, "DetachSandboxProvider request completed successfully" @@ -1324,21 +1347,15 @@ async fn handle_delete_sandbox_inner( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - let name = req.name; - if name.is_empty() { - return Err(Status::invalid_argument("name is required")); - } - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::User, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; + let workspace = sandbox.object_workspace().to_string(); + let name = sandbox.object_name().to_string(); let result = state.compute.delete_sandbox(&workspace, &name).await?; if result.deleted { @@ -1373,22 +1390,17 @@ async fn handle_stop_sandbox_inner( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - if req.name.is_empty() { - return Err(Status::invalid_argument("name is required")); - } - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let resolved = resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::User, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; - let sandbox = state.compute.stop_sandbox(&workspace, &req.name).await?; - info!(sandbox_name = %req.name, "StopSandbox request completed successfully"); + let workspace = resolved.object_workspace(); + let name = resolved.object_name(); + let sandbox = state.compute.stop_sandbox(workspace, name).await?; + info!(sandbox_name = %name, "StopSandbox request completed successfully"); Ok(Response::new(SandboxResponse { sandbox: Some(sandbox), })) @@ -1417,44 +1429,22 @@ async fn handle_start_sandbox_inner( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - if req.name.is_empty() { - return Err(Status::invalid_argument("name is required")); - } - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let resolved = resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::User, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; - let sandbox = state.compute.start_sandbox(&workspace, &req.name).await?; - info!(sandbox_name = %req.name, "StartSandbox request completed successfully"); + let workspace = resolved.object_workspace(); + let name = resolved.object_name(); + let sandbox = state.compute.start_sandbox(workspace, name).await?; + info!(sandbox_name = %name, "StartSandbox request completed successfully"); Ok(Response::new(SandboxResponse { sandbox: Some(sandbox), })) } -async fn sandbox_by_name( - state: &Arc, - workspace: &str, - name: &str, -) -> Result { - if name.is_empty() { - return Err(Status::invalid_argument("sandbox_name is required")); - } - - state - .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")) -} - async fn providers_for_sandbox( state: &Arc, sandbox: &Sandbox, @@ -1503,12 +1493,14 @@ pub(super) async fn handle_watch_sandbox( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - if req.id.is_empty() { - return Err(Status::invalid_argument("id is required")); - } - let sandbox_id = req.id.clone(); - - let _sandbox = fetch_and_authorize_sandbox(state, &principal, &sandbox_id).await?; + let sandbox = resolve_and_authorize_sandbox_reference( + state, + &principal, + req.sandbox_ref.as_ref(), + MinWorkspaceRole::User, + ) + .await?; + let sandbox_id = sandbox.object_id().to_string(); let follow_status = req.follow_status; let follow_logs = req.follow_logs; @@ -1762,9 +1754,6 @@ pub(super) async fn handle_exec_sandbox( let principal = super::extract_principal(&request)?; let req = request.into_inner(); - if req.sandbox_id.is_empty() { - return Err(Status::invalid_argument("sandbox_id is required")); - } if req.command.is_empty() { return Err(Status::invalid_argument("command is required")); } @@ -1775,7 +1764,13 @@ pub(super) async fn handle_exec_sandbox( } validate_exec_request_fields(&req)?; - let sandbox = fetch_and_authorize_sandbox(state, &principal, &req.sandbox_id).await?; + let sandbox = resolve_and_authorize_sandbox_reference( + state, + &principal, + req.sandbox_ref.as_ref(), + MinWorkspaceRole::User, + ) + .await?; if SandboxPhase::try_from(sandbox.phase()).ok() != Some(SandboxPhase::Ready) { return Err(Status::failed_precondition("sandbox is not ready")); @@ -1891,7 +1886,13 @@ pub(super) async fn handle_forward_tcp( let target = validate_tcp_forward_init(&init)?; - let sandbox = fetch_and_authorize_sandbox(state, &principal, &init.sandbox_id).await?; + let sandbox = resolve_and_authorize_sandbox_reference( + state, + &principal, + init.sandbox_ref.as_ref(), + MinWorkspaceRole::User, + ) + .await?; // The main process may finish between minting the SSH token and opening // its transport. Keep the relay reachable until terminal delivery is @@ -2049,10 +2050,6 @@ fn decrement_ssh_connection_count(counts: &std::sync::Mutex } fn validate_tcp_forward_init(init: &TcpForwardInit) -> Result { - if init.sandbox_id.is_empty() { - return Err(Status::invalid_argument("sandbox_id is required")); - } - if let Some(target) = init.target.as_ref() { return match target { tcp_forward_init::Target::Ssh(_) => { @@ -2184,9 +2181,6 @@ fn validate_interactive_exec_start( )); }; - if req.sandbox_id.is_empty() { - return Err(Status::invalid_argument("sandbox_id is required")); - } if req.command.is_empty() { return Err(Status::invalid_argument("command is required")); } @@ -2216,7 +2210,13 @@ pub(super) async fn handle_exec_sandbox_interactive( let req = validate_interactive_exec_start(first_msg)?; - let sandbox = fetch_and_authorize_sandbox(state, &principal, &req.sandbox_id).await?; + let sandbox = resolve_and_authorize_sandbox_reference( + state, + &principal, + req.sandbox_ref.as_ref(), + MinWorkspaceRole::User, + ) + .await?; if SandboxPhase::try_from(sandbox.phase()).ok() != Some(SandboxPhase::Ready) { return Err(Status::failed_precondition("sandbox is not ready")); @@ -2294,11 +2294,14 @@ pub(super) async fn handle_create_ssh_session( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - if req.sandbox_id.is_empty() { - return Err(Status::invalid_argument("sandbox_id is required")); - } - - let sandbox = fetch_and_authorize_sandbox(state, &principal, &req.sandbox_id).await?; + let sandbox = resolve_and_authorize_sandbox_reference( + state, + &principal, + req.sandbox_ref.as_ref(), + MinWorkspaceRole::User, + ) + .await?; + let sandbox_id = sandbox.object_id().to_string(); if !sandbox_relay_reachable(state, &sandbox) { return Err(Status::failed_precondition("sandbox is not ready")); @@ -2322,7 +2325,7 @@ pub(super) async fn handle_create_ssh_session( workspace: sandbox.object_workspace().to_string(), deletion_timestamp_ms: 0, }), - sandbox_id: req.sandbox_id.clone(), + sandbox_id: sandbox_id.clone(), token: token.clone(), revoked: false, expires_at_ms, @@ -2363,7 +2366,7 @@ pub(super) async fn handle_create_ssh_session( }; Ok(Response::new(CreateSshSessionResponse { - sandbox_id: req.sandbox_id, + sandbox_id, token, gateway_host, gateway_port: gateway_port.into(), @@ -3265,7 +3268,9 @@ mod tests { fn build_remote_exec_command_basic() { use openshell_core::proto::ExecSandboxRequest; let req = ExecSandboxRequest { - sandbox_id: "test".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "test".to_string(), + )), command: vec!["ls".to_string(), "-la".to_string()], ..Default::default() }; @@ -3276,7 +3281,9 @@ mod tests { fn build_remote_exec_command_with_env_and_workdir() { use openshell_core::proto::ExecSandboxRequest; let req = ExecSandboxRequest { - sandbox_id: "test".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "test".to_string(), + )), command: vec![ "python".to_string(), "-c".to_string(), @@ -3296,7 +3303,9 @@ mod tests { fn build_remote_exec_command_rejects_null_bytes_in_args() { use openshell_core::proto::ExecSandboxRequest; let req = ExecSandboxRequest { - sandbox_id: "test".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "test".to_string(), + )), command: vec!["echo".to_string(), "hello\x00world".to_string()], ..Default::default() }; @@ -3307,7 +3316,9 @@ mod tests { fn build_remote_exec_command_rejects_newlines_in_workdir() { use openshell_core::proto::ExecSandboxRequest; let req = ExecSandboxRequest { - sandbox_id: "test".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "test".to_string(), + )), command: vec!["ls".to_string()], workdir: "/tmp\nmalicious".to_string(), ..Default::default() @@ -3320,7 +3331,9 @@ mod tests { fn build_remote_exec_command_accepts_multiline_script() { use openshell_core::proto::ExecSandboxRequest; let req = ExecSandboxRequest { - sandbox_id: "test".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "test".to_string(), + )), command: vec![ "python3".to_string(), "-c".to_string(), @@ -3337,7 +3350,9 @@ mod tests { fn build_remote_exec_command_multiline_with_single_quotes() { use openshell_core::proto::ExecSandboxRequest; let req = ExecSandboxRequest { - sandbox_id: "test".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "test".to_string(), + )), command: vec![ "python3".to_string(), "-c".to_string(), @@ -3357,7 +3372,9 @@ mod tests { fn tcp_forward_init_allows_loopback_targets() { for host in ["127.0.0.1", "::1", "localhost"] { let init = TcpForwardInit { - sandbox_id: "sbx".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sbx".to_string(), + )), service_id: String::new(), target: Some(tcp_forward_init::Target::Tcp(TcpRelayTarget { host: host.to_string(), @@ -3372,7 +3389,9 @@ mod tests { #[test] fn tcp_forward_init_allows_ssh_target() { let init = TcpForwardInit { - sandbox_id: "sbx".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sbx".to_string(), + )), target: Some(tcp_forward_init::Target::Ssh(SshRelayTarget::default())), ..Default::default() }; @@ -3385,7 +3404,9 @@ mod tests { #[test] fn tcp_forward_init_rejects_non_loopback_targets() { let init = TcpForwardInit { - sandbox_id: "sbx".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sbx".to_string(), + )), service_id: String::new(), target: Some(tcp_forward_init::Target::Tcp(TcpRelayTarget { host: "example.com".to_string(), @@ -3404,7 +3425,9 @@ mod tests { #[test] fn tcp_forward_init_rejects_invalid_port() { let init = TcpForwardInit { - sandbox_id: "sbx".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sbx".to_string(), + )), service_id: String::new(), target: Some(tcp_forward_init::Target::Tcp(TcpRelayTarget { host: "127.0.0.1".to_string(), @@ -3423,7 +3446,9 @@ mod tests { #[test] fn tcp_forward_init_requires_target() { let init = TcpForwardInit { - sandbox_id: "sbx".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sbx".to_string(), + )), ..Default::default() }; assert_eq!( @@ -3586,7 +3611,9 @@ mod tests { handle_watch_sandbox( &state, authed_request(WatchSandboxRequest { - id: sandbox.object_id().to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox.object_id().to_string(), + )), ..Default::default() }), ) @@ -3629,9 +3656,9 @@ mod tests { handle_delete_sandbox_inner( &delete_state, authed_request(DeleteSandboxRequest { - name: "reused-name".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "reused-name".to_string(), )), }), ) @@ -3688,10 +3715,12 @@ mod tests { let response = handle_attach_sandbox_provider( &state, authed_request(AttachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: "work-github".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -3729,10 +3758,12 @@ mod tests { let response = handle_attach_sandbox_provider( &state, authed_request(AttachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: "work-github".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -3767,10 +3798,12 @@ mod tests { let response = handle_attach_sandbox_provider( &state, authed_request(AttachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: "work-github".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -3818,10 +3851,12 @@ mod tests { let response = handle_detach_sandbox_provider( &state, authed_request(DetachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: "work-github".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -3843,10 +3878,12 @@ mod tests { let response = handle_detach_sandbox_provider( &state, authed_request(DetachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: "work-github".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -3890,10 +3927,12 @@ mod tests { let error = handle_detach_sandbox_provider( &state, authed_request(DetachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: "work-gcp".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -3930,8 +3969,10 @@ mod tests { let response = handle_list_sandbox_providers( &state, authed_request(ListSandboxProvidersRequest { - sandbox_name: "work".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), }), ) .await @@ -3958,10 +3999,12 @@ mod tests { let err = handle_attach_sandbox_provider( &state, authed_request(AttachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: "missing".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -4021,7 +4064,7 @@ mod tests { }; let err = validate_interactive_exec_start(Some(msg)).unwrap_err(); assert_eq!(err.code(), tonic::Code::InvalidArgument); - assert!(err.message().contains("sandbox_id")); + assert!(err.message().contains("sandbox_ref")); } #[test] @@ -4029,7 +4072,9 @@ mod tests { use openshell_core::proto::exec_sandbox_input; let msg = ExecSandboxInput { payload: Some(exec_sandbox_input::Payload::Start(ExecSandboxRequest { - sandbox_id: "test-id".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "test-id".to_string(), + )), ..Default::default() })), }; @@ -4043,7 +4088,9 @@ mod tests { use openshell_core::proto::exec_sandbox_input; let msg = ExecSandboxInput { payload: Some(exec_sandbox_input::Payload::Start(ExecSandboxRequest { - sandbox_id: "test-id".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "test-id".to_string(), + )), command: vec!["bash".to_string()], environment: std::iter::once(("bad key!".to_string(), "val".to_string())).collect(), ..Default::default() @@ -4059,7 +4106,9 @@ mod tests { use openshell_core::proto::exec_sandbox_input; let msg = ExecSandboxInput { payload: Some(exec_sandbox_input::Payload::Start(ExecSandboxRequest { - sandbox_id: "test-id".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "test-id".to_string(), + )), command: vec!["bash".to_string()], tty: true, cols: 120, @@ -4068,7 +4117,10 @@ mod tests { })), }; let req = validate_interactive_exec_start(Some(msg)).unwrap(); - assert_eq!(req.sandbox_id, "test-id"); + assert_eq!( + req.sandbox_ref.unwrap().identifier, + Some(sandbox_reference::Identifier::Id("test-id".to_string())) + ); assert_eq!(req.command, vec!["bash"]); assert!(req.tty); assert_eq!(req.cols, 120); @@ -4080,14 +4132,20 @@ mod tests { let state = test_server_state().await; let req = ExecSandboxRequest { - sandbox_id: "nonexistent".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "nonexistent".to_string(), + )), command: vec!["bash".to_string()], tty: true, ..Default::default() }; + let sandbox_id = match req.sandbox_ref.unwrap().identifier.unwrap() { + sandbox_reference::Identifier::Id(id) => id, + sandbox_reference::Identifier::Name(_) => unreachable!(), + }; let sandbox_result = state .store - .get_message::(&req.sandbox_id) + .get_message::(&sandbox_id) .await .unwrap(); assert!(sandbox_result.is_none()); @@ -4583,8 +4641,10 @@ mod tests { let fetched = handle_get_sandbox( &state, authed_request(GetSandboxRequest { - name: "annotated".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "annotated".to_string(), + )), }), ) .await @@ -4647,8 +4707,10 @@ mod tests { let fetched_process = handle_get_sandbox( &state, authed_request(GetSandboxRequest { - name: "partial-id".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "partial-id".to_string(), + )), }), ) .await @@ -5660,10 +5722,12 @@ mod tests { let err = handle_attach_sandbox_provider( &state, authed_request(AttachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: "provider-b".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -5707,10 +5771,12 @@ mod tests { let response = handle_attach_sandbox_provider( &state, authed_request(AttachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: "provider-31".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -5762,10 +5828,12 @@ mod tests { let err = handle_attach_sandbox_provider( &state, authed_request(AttachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: "provider-32".to_string(), expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -5809,10 +5877,12 @@ mod tests { let err = handle_attach_sandbox_provider( &state, authed_request(AttachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: long_name, expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -5836,10 +5906,12 @@ mod tests { let err = handle_detach_sandbox_provider( &state, authed_request(DetachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: long_name, expected_resource_version: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -5865,7 +5937,9 @@ mod tests { handle_create_ssh_session( &state1, authed_request(CreateSshSessionRequest { - sandbox_id: "sandbox-work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sandbox-work".to_string(), + )), }), ) .await @@ -5876,7 +5950,9 @@ mod tests { handle_create_ssh_session( &state2, authed_request(CreateSshSessionRequest { - sandbox_id: "sandbox-work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sandbox-work".to_string(), + )), }), ) .await @@ -5929,7 +6005,9 @@ mod tests { let response = handle_create_ssh_session( &state, authed_request(CreateSshSessionRequest { - sandbox_id: "sandbox-work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sandbox-work".to_string(), + )), }), ) .await; @@ -5957,7 +6035,9 @@ mod tests { let response = handle_create_ssh_session( &state, authed_request(CreateSshSessionRequest { - sandbox_id: "sandbox-work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sandbox-work".to_string(), + )), }), ) .await @@ -6035,10 +6115,12 @@ mod tests { let response = handle_attach_sandbox_provider( &state, authed_request(AttachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: "github".to_string(), expected_resource_version: current_version, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -6087,10 +6169,12 @@ mod tests { let err = handle_attach_sandbox_provider( &state, authed_request(AttachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: "github".to_string(), expected_resource_version: 99, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -6150,10 +6234,12 @@ mod tests { let response = handle_detach_sandbox_provider( &state, authed_request(DetachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: "github".to_string(), expected_resource_version: current_version, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -6202,10 +6288,12 @@ mod tests { let err = handle_detach_sandbox_provider( &state, authed_request(DetachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: "github".to_string(), expected_resource_version: 99, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -6283,10 +6371,12 @@ mod tests { handle_attach_sandbox_provider( &state_clone, authed_request(AttachSandboxProviderRequest { - sandbox_name: "work".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + "work".to_string(), + )), provider_name: format!("provider-{i}"), expected_resource_version: initial_version, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), }), ) .await @@ -6369,9 +6459,9 @@ mod tests { let got = handle_get_sandbox( &state, authed_request(GetSandboxRequest { - name: "shared-name".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "shared-name".to_string(), )), }), ) @@ -6384,9 +6474,9 @@ mod tests { let got = handle_get_sandbox( &state, authed_request(GetSandboxRequest { - name: "shared-name".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "beta".to_string(), + "shared-name".to_string(), )), }), ) @@ -6459,9 +6549,9 @@ mod tests { let got = handle_get_sandbox( &state, authed_request(GetSandboxRequest { - name: "shared-name".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "beta".to_string(), + "shared-name".to_string(), )), }), ) @@ -6499,10 +6589,9 @@ mod tests { assert_eq!(listed.sandboxes.len(), 2); } - /// Non-members must receive `PERMISSION_DENIED` — never `NOT_FOUND` — when - /// calling workspace-scoped sandbox RPCs with a workspace they do not belong - /// to. If `authorize_workspace` ran *after* a store lookup the error code - /// would leak whether the workspace name exists (CWE-203 oracle). + /// Workspace collection operations reject non-members, while operations on + /// a sandbox reference hide both missing and unauthorized sandboxes as + /// `NOT_FOUND` to avoid an object-existence oracle. #[tokio::test] async fn non_member_gets_permission_denied_not_workspace_oracle() { use crate::auth::identity::{Identity, IdentityProvider}; @@ -6550,16 +6639,18 @@ mod tests { let err = handle_get_sandbox( &state, non_member_request(GetSandboxRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), - name: "any".into(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), }), ) .await .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_get_sandbox should reject non-members with PermissionDenied" + Code::NotFound, + "handle_get_sandbox should hide unauthorized sandbox existence" ); // --- handle_list_sandboxes --- @@ -6582,23 +6673,28 @@ mod tests { let err = handle_list_sandbox_providers( &state, non_member_request(ListSandboxProvidersRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), - ..Default::default() + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), }), ) .await .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_list_sandbox_providers should reject non-members with PermissionDenied" + Code::NotFound, + "handle_list_sandbox_providers should hide unauthorized sandbox existence" ); // --- handle_attach_sandbox_provider --- let err = handle_attach_sandbox_provider( &state, non_member_request(AttachSandboxProviderRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), ..Default::default() }), ) @@ -6606,15 +6702,18 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_attach_sandbox_provider should reject non-members with PermissionDenied" + Code::NotFound, + "handle_attach_sandbox_provider should hide unauthorized sandbox existence" ); // --- handle_detach_sandbox_provider --- let err = handle_detach_sandbox_provider( &state, non_member_request(DetachSandboxProviderRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), ..Default::default() }), ) @@ -6622,8 +6721,8 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_detach_sandbox_provider should reject non-members with PermissionDenied" + Code::NotFound, + "handle_detach_sandbox_provider should hide unauthorized sandbox existence" ); // --- handle_delete_sandbox --- @@ -6631,40 +6730,46 @@ mod tests { let err = handle_delete_sandbox( &state, non_member_request(DeleteSandboxRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), - name: "any".into(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), }), ) .await .unwrap_err(); assert_eq!( err.code(), - Code::PermissionDenied, - "handle_delete_sandbox should reject non-members with PermissionDenied" + Code::NotFound, + "handle_delete_sandbox should hide unauthorized sandbox existence" ); for result in [ handle_stop_sandbox( &state, non_member_request(StopSandboxRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), - name: "any".into(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), }), ) .await, handle_start_sandbox( &state, non_member_request(StartSandboxRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), - name: "any".into(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), }), ) .await, ] { assert_eq!( result.unwrap_err().code(), - Code::PermissionDenied, - "lifecycle handlers should reject non-members" + Code::NotFound, + "lifecycle handlers should hide unauthorized sandbox existence" ); } } @@ -6703,7 +6808,9 @@ mod tests { let err = handle_watch_sandbox( &state, non_member_request(WatchSandboxRequest { - id: "sandbox-cross-ws".into(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sandbox-cross-ws", + )), ..Default::default() }), ) @@ -6719,7 +6826,9 @@ mod tests { let err = handle_create_ssh_session( &state, non_member_request(CreateSshSessionRequest { - sandbox_id: "sandbox-cross-ws".into(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sandbox-cross-ws", + )), }), ) .await @@ -6743,7 +6852,9 @@ mod tests { let response = handle_create_ssh_session( &state, authed_request(CreateSshSessionRequest { - sandbox_id: "sandbox-ws-test".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "sandbox-ws-test".to_string(), + )), }), ) .await diff --git a/crates/openshell-server/src/grpc/service.rs b/crates/openshell-server/src/grpc/service.rs index 34c7d62d3..9546668de 100644 --- a/crates/openshell-server/src/grpc/service.rs +++ b/crates/openshell-server/src/grpc/service.rs @@ -7,9 +7,9 @@ use std::sync::Arc; use openshell_core::proto::datamodel::v1::ObjectMeta; use openshell_core::proto::{ DeleteServiceRequest, DeleteServiceResponse, ExposeServiceRequest, GetServiceRequest, - ListServicesRequest, ListServicesResponse, Sandbox, ServiceEndpoint, ServiceEndpointResponse, + ListServicesRequest, ListServicesResponse, ServiceEndpoint, ServiceEndpointResponse, }; -use openshell_core::{ObjectId, ObjectWorkspace}; +use openshell_core::{ObjectId, ObjectName, ObjectWorkspace}; use prost::Message as _; use tonic::{Request, Response, Status}; use uuid::Uuid; @@ -17,13 +17,11 @@ use uuid::Uuid; use crate::ServerState; use crate::auth::workspace_authz::{ AuthorizedWorkspaceScope, MinWorkspaceRole, authorize_list_workspace_selector, - authorize_workspace_selector, }; use crate::persistence::{ObjectType, WriteCondition}; use crate::service_routing; const MAX_SERVICE_NAME_LEN: usize = super::MAX_ROUTABLE_NAME_LEN; -const MAX_SANDBOX_NAME_LEN: usize = super::MAX_ROUTABLE_NAME_LEN; pub(super) async fn handle_expose_service( state: &Arc, @@ -31,32 +29,25 @@ pub(super) async fn handle_expose_service( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::User, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .ensure_active()?; - validate_endpoint_name("sandbox", &req.sandbox, MAX_SANDBOX_NAME_LEN)?; + let workspace = + super::workspace::resolve_workspace(state.store.as_ref(), sandbox.object_workspace()) + .await? + .ensure_active()?; + let sandbox_name = sandbox.object_name(); validate_optional_endpoint_name("service", &req.service, MAX_SERVICE_NAME_LEN)?; if req.target_port == 0 || req.target_port > u32::from(u16::MAX) { return Err(Status::invalid_argument("target_port must be in 1..=65535")); } - let sandbox = 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 now = crate::persistence::current_time_ms(); - let key = service_routing::endpoint_key(&req.sandbox, &req.service); + let key = service_routing::endpoint_key(sandbox_name, &req.service); // Fetch existing endpoint to determine create vs. update path let existing = state @@ -92,7 +83,7 @@ pub(super) async fn handle_expose_service( let labels_json = serde_json::to_string(&HashMap::from([( "sandbox".to_string(), - req.sandbox.clone(), + sandbox_name.to_string(), )])) .map_err(|e| Status::internal(format!("serialize labels failed: {e}")))?; @@ -101,14 +92,14 @@ pub(super) async fn handle_expose_service( id: id.clone(), name: key.clone(), created_at_ms, - labels: HashMap::from([("sandbox".to_string(), req.sandbox.clone())]), + labels: HashMap::from([("sandbox".to_string(), sandbox_name.to_string())]), resource_version: 0, annotations: HashMap::new(), workspace: workspace.clone(), deletion_timestamp_ms: 0, }), sandbox_id: sandbox.object_id().to_string(), - sandbox_name: req.sandbox.clone(), + sandbox_name: sandbox_name.to_string(), service_name: req.service.clone(), target_port: req.target_port, domain: true, @@ -134,7 +125,7 @@ pub(super) async fn handle_expose_service( meta.resource_version = result.resource_version; } - let url = service_routing::endpoint_url(&state.config, &workspace, &req.sandbox, &req.service) + let url = service_routing::endpoint_url(&state.config, &workspace, sandbox_name, &req.service) .unwrap_or_default(); service_routing::emit_service_endpoint_config_event(&endpoint, &url, created); @@ -150,21 +141,18 @@ pub(super) async fn handle_get_service( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::User, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; - validate_endpoint_name("sandbox", &req.sandbox, MAX_SANDBOX_NAME_LEN)?; + let workspace = sandbox.object_workspace(); + let sandbox_name = sandbox.object_name(); validate_optional_endpoint_name("service", &req.service, MAX_SERVICE_NAME_LEN)?; - let endpoint = get_service_endpoint(state, &workspace, &req.sandbox, &req.service) + let endpoint = get_service_endpoint(state, workspace, sandbox_name, &req.service) .await? .ok_or_else(|| Status::not_found("service endpoint not found"))?; @@ -177,11 +165,36 @@ pub(super) async fn handle_list_services( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - if !req.sandbox.is_empty() { - validate_endpoint_name("sandbox", &req.sandbox, MAX_SANDBOX_NAME_LEN)?; - } - let limit = super::clamp_limit(req.limit, 100, super::MAX_PAGE_SIZE); + if req.sandbox_ref.is_some() { + if req.workspace_scope.is_some() { + return Err(Status::invalid_argument( + "workspace_scope must be omitted when sandbox_ref is set", + )); + } + let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference( + state, + &principal, + req.sandbox_ref.as_ref(), + MinWorkspaceRole::User, + ) + .await?; + let endpoints = state + .store + .list_messages_with_selector::( + sandbox.object_workspace(), + &format!("sandbox={}", sandbox.object_name()), + limit, + req.offset, + ) + .await + .map_err(|e| Status::internal(format!("list endpoints failed: {e}")))?; + let services = endpoints + .into_iter() + .map(|endpoint| service_endpoint_response(state, endpoint)) + .collect(); + return Ok(Response::new(ListServicesResponse { services })); + } let scope = authorize_list_workspace_selector( &state.store, &state.admin_role, @@ -192,11 +205,6 @@ pub(super) async fn handle_list_services( .await?; let endpoints: Vec = if matches!(scope, AuthorizedWorkspaceScope::AllWorkspaces) { - if !req.sandbox.is_empty() { - return Err(Status::invalid_argument( - "sandbox filter is not supported with all_workspaces", - )); - } state.store.list_all_messages(limit, req.offset).await } else { let AuthorizedWorkspaceScope::Workspace(authz) = scope else { @@ -206,22 +214,10 @@ pub(super) async fn handle_list_services( super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) .await? .name; - if req.sandbox.is_empty() { - state - .store - .list_messages(&workspace, limit, req.offset) - .await - } else { - state - .store - .list_messages_with_selector( - &workspace, - &format!("sandbox={}", req.sandbox), - limit, - req.offset, - ) - .await - } + state + .store + .list_messages(&workspace, limit, req.offset) + .await } .map_err(|e| Status::internal(format!("list endpoints failed: {e}")))?; @@ -239,29 +235,26 @@ pub(super) async fn handle_delete_service( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); - let authz = authorize_workspace_selector( - &state.store, - &state.admin_role, + let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference( + state, &principal, - req.workspace_scope.as_ref(), + req.sandbox_ref.as_ref(), MinWorkspaceRole::User, ) .await?; - let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) - .await? - .name; - validate_endpoint_name("sandbox", &req.sandbox, MAX_SANDBOX_NAME_LEN)?; + let workspace = sandbox.object_workspace(); + let sandbox_name = sandbox.object_name(); validate_optional_endpoint_name("service", &req.service, MAX_SERVICE_NAME_LEN)?; - let endpoint = get_service_endpoint(state, &workspace, &req.sandbox, &req.service).await?; + let endpoint = get_service_endpoint(state, workspace, sandbox_name, &req.service).await?; let Some(endpoint) = endpoint else { return Ok(Response::new(DeleteServiceResponse { deleted: false })); }; - let key = service_routing::endpoint_key(&req.sandbox, &req.service); + let key = service_routing::endpoint_key(sandbox_name, &req.service); let deleted = state .store - .delete_by_name(ServiceEndpoint::object_type(), &workspace, &key) + .delete_by_name(ServiceEndpoint::object_type(), workspace, &key) .await .map_err(|e| Status::internal(format!("delete endpoint failed: {e}")))?; @@ -305,6 +298,7 @@ fn service_endpoint_response( } #[allow(clippy::result_large_err)] +#[cfg(test)] fn validate_endpoint_name(field: &str, value: &str, max_len: usize) -> Result<(), Status> { if value.is_empty() { return Err(Status::invalid_argument(format!("{field} is required"))); @@ -357,7 +351,7 @@ fn is_dns_label(value: &str) -> bool { mod tests { use super::*; use crate::grpc::test_support::{authed_request, test_server_state}; - use openshell_core::proto::SandboxPhase; + use openshell_core::proto::{Sandbox, SandboxPhase}; async fn seed_sandbox(state: &Arc, name: &str) { let mut sandbox = Sandbox { @@ -411,13 +405,13 @@ mod tests { let exposed = handle_expose_service( &state, authed_request(ExposeServiceRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "my-sandbox".to_string(), + )), service: "web".to_string(), target_port: 8080, domain: true, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -428,12 +422,13 @@ mod tests { let listed = handle_list_services( &state, authed_request(ListServicesRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "my-sandbox".to_string(), + )), + workspace_scope: None, limit: 0, offset: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -448,11 +443,11 @@ mod tests { let fetched = handle_get_service( &state, authed_request(GetServiceRequest { - sandbox: "my-sandbox".to_string(), - service: "web".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "my-sandbox".to_string(), )), + service: "web".to_string(), }), ) .await @@ -463,11 +458,11 @@ mod tests { let deleted = handle_delete_service( &state, authed_request(DeleteServiceRequest { - sandbox: "my-sandbox".to_string(), - service: "web".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "my-sandbox".to_string(), )), + service: "web".to_string(), }), ) .await @@ -478,11 +473,11 @@ mod tests { let err = handle_get_service( &state, authed_request(GetServiceRequest { - sandbox: "my-sandbox".to_string(), - service: "web".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "my-sandbox".to_string(), )), + service: "web".to_string(), }), ) .await @@ -492,12 +487,13 @@ mod tests { let listed = handle_list_services( &state, authed_request(ListServicesRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "my-sandbox".to_string(), + )), + workspace_scope: None, limit: 0, offset: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -517,13 +513,13 @@ mod tests { handle_expose_service( &state1, authed_request(ExposeServiceRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "my-sandbox".to_string(), + )), service: "web".to_string(), target_port: 8080, domain: true, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -534,13 +530,13 @@ mod tests { handle_expose_service( &state2, authed_request(ExposeServiceRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "my-sandbox".to_string(), + )), service: "web".to_string(), target_port: 9090, domain: true, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -562,12 +558,13 @@ mod tests { let listed = handle_list_services( &state, authed_request(ListServicesRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "my-sandbox".to_string(), + )), + workspace_scope: None, limit: 0, offset: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -585,13 +582,13 @@ mod tests { handle_expose_service( &state, authed_request(ExposeServiceRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "my-sandbox".to_string(), + )), service: "web".to_string(), target_port: 7070, domain: true, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -603,13 +600,13 @@ mod tests { handle_expose_service( &state1, authed_request(ExposeServiceRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "my-sandbox".to_string(), + )), service: "web".to_string(), target_port: 8080, domain: true, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -620,13 +617,13 @@ mod tests { handle_expose_service( &state2, authed_request(ExposeServiceRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "my-sandbox".to_string(), + )), service: "web".to_string(), target_port: 9090, domain: true, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -647,11 +644,11 @@ mod tests { let fetched = handle_get_service( &state, authed_request(GetServiceRequest { - sandbox: "my-sandbox".to_string(), - service: "web".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "my-sandbox".to_string(), )), + service: "web".to_string(), }), ) .await @@ -708,13 +705,13 @@ mod tests { handle_expose_service( &state, authed_request(ExposeServiceRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "my-sandbox".to_string(), + )), service: "web".to_string(), target_port: 8080, domain: true, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -723,13 +720,13 @@ mod tests { handle_expose_service( &state, authed_request(ExposeServiceRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "beta".to_string(), + "my-sandbox".to_string(), + )), service: "web".to_string(), target_port: 9090, domain: true, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "beta".to_string(), - )), }), ) .await @@ -739,11 +736,11 @@ mod tests { let got = handle_get_service( &state, authed_request(GetServiceRequest { - sandbox: "my-sandbox".to_string(), - service: "web".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "my-sandbox".to_string(), )), + service: "web".to_string(), }), ) .await @@ -755,11 +752,11 @@ mod tests { let got = handle_get_service( &state, authed_request(GetServiceRequest { - sandbox: "my-sandbox".to_string(), - service: "web".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "beta".to_string(), + "my-sandbox".to_string(), )), + service: "web".to_string(), }), ) .await @@ -771,12 +768,13 @@ mod tests { let listed = handle_list_services( &state, authed_request(ListServicesRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "my-sandbox".to_string(), + )), + workspace_scope: None, limit: 100, offset: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -791,12 +789,13 @@ mod tests { let listed = handle_list_services( &state, authed_request(ListServicesRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "beta".to_string(), + "my-sandbox".to_string(), + )), + workspace_scope: None, limit: 100, offset: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "beta".to_string(), - )), }), ) .await @@ -812,11 +811,11 @@ mod tests { let deleted = handle_delete_service( &state, authed_request(DeleteServiceRequest { - sandbox: "my-sandbox".to_string(), - service: "web".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "default".to_string(), + "my-sandbox".to_string(), )), + service: "web".to_string(), }), ) .await @@ -827,12 +826,13 @@ mod tests { let listed = handle_list_services( &state, authed_request(ListServicesRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "my-sandbox".to_string(), + )), + workspace_scope: None, limit: 100, offset: 0, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -843,11 +843,11 @@ mod tests { let got = handle_get_service( &state, authed_request(GetServiceRequest { - sandbox: "my-sandbox".to_string(), - service: "web".to_string(), - workspace_scope: Some(openshell_core::proto::workspace_selector( + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( "beta".to_string(), + "my-sandbox".to_string(), )), + service: "web".to_string(), }), ) .await @@ -860,13 +860,13 @@ mod tests { handle_expose_service( &state, authed_request(ExposeServiceRequest { - sandbox: "my-sandbox".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default".to_string(), + "my-sandbox".to_string(), + )), service: "api".to_string(), target_port: 3000, domain: true, - workspace_scope: Some(openshell_core::proto::workspace_selector( - "default".to_string(), - )), }), ) .await @@ -875,10 +875,10 @@ mod tests { let listed = handle_list_services( &state, authed_request(ListServicesRequest { - sandbox: String::new(), + sandbox_ref: None, + workspace_scope: Some(openshell_core::proto::all_workspaces_selector()), limit: 100, offset: 0, - workspace_scope: Some(openshell_core::proto::all_workspaces_selector()), }), ) .await @@ -891,7 +891,7 @@ mod tests { /// when targeting a workspace that does not exist. Returning `NOT_FOUND` /// would create a CWE-203 workspace-name oracle. #[tokio::test] - async fn non_member_gets_permission_denied_not_workspace_oracle() { + async fn non_member_gets_gets_not_found_without_sandbox_oracle() { use crate::auth::identity::{Identity, IdentityProvider}; use crate::auth::principal::{Principal, UserPrincipal}; @@ -915,7 +915,10 @@ mod tests { let err = handle_expose_service( &state, non_member_request(ExposeServiceRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), ..Default::default() }), ) @@ -923,15 +926,18 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - tonic::Code::PermissionDenied, - "handle_expose_service should return PermissionDenied, got {:?}", + tonic::Code::NotFound, + "handle_expose_service should return NotFound, got {:?}", err.code() ); let err = handle_get_service( &state, non_member_request(GetServiceRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), ..Default::default() }), ) @@ -939,15 +945,19 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - tonic::Code::PermissionDenied, - "handle_get_service should return PermissionDenied, got {:?}", + tonic::Code::NotFound, + "handle_get_service should return NotFound, got {:?}", err.code() ); let err = handle_list_services( &state, non_member_request(ListServicesRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), + workspace_scope: None, ..Default::default() }), ) @@ -955,15 +965,18 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - tonic::Code::PermissionDenied, - "handle_list_services should return PermissionDenied, got {:?}", + tonic::Code::NotFound, + "handle_list_services should return NotFound, got {:?}", err.code() ); let err = handle_delete_service( &state, non_member_request(DeleteServiceRequest { - workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "no-such-ws", + "any", + )), ..Default::default() }), ) @@ -971,8 +984,8 @@ mod tests { .unwrap_err(); assert_eq!( err.code(), - tonic::Code::PermissionDenied, - "handle_delete_service should return PermissionDenied, got {:?}", + tonic::Code::NotFound, + "handle_delete_service should return NotFound, got {:?}", err.code() ); } diff --git a/crates/openshell-server/src/grpc/validation.rs b/crates/openshell-server/src/grpc/validation.rs index dac34524c..697ff9f64 100644 --- a/crates/openshell-server/src/grpc/validation.rs +++ b/crates/openshell-server/src/grpc/validation.rs @@ -43,6 +43,9 @@ pub(super) const MAX_MAIN_PROCESS_ARGV_SIZE: usize = 256 * 1024; /// Command arguments only reject NUL (newlines are valid for inline scripts). /// Environment values and workdir reject both NUL and newlines. pub(super) fn validate_exec_request_fields(req: &ExecSandboxRequest) -> Result<(), Status> { + if req.sandbox_ref.is_none() { + return Err(Status::invalid_argument("sandbox_ref is required")); + } if req.command.len() > MAX_EXEC_COMMAND_ARGS { return Err(Status::invalid_argument(format!( "command array exceeds {MAX_EXEC_COMMAND_ARGS} argument limit" @@ -1351,7 +1354,9 @@ mod tests { #[test] fn validate_exec_request_rejects_reserved_env_key() { let req = ExecSandboxRequest { - sandbox_id: "id".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "id".to_string(), + )), command: vec!["echo".to_string()], environment: std::iter::once(("OPENSHELL_SANDBOX_ID".to_string(), "evil".to_string())) .collect(), @@ -1368,7 +1373,9 @@ mod tests { #[test] fn validate_exec_request_allows_pyfunc_helper_key() { let req = ExecSandboxRequest { - sandbox_id: "id".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "id".to_string(), + )), command: vec!["python".to_string()], environment: std::iter::once(("OPENSHELL_PYFUNC_B64".to_string(), "data".to_string())) .collect(), @@ -2246,7 +2253,9 @@ mod tests { #[test] fn validate_exec_allows_newlines_in_command_args() { let req = ExecSandboxRequest { - sandbox_id: "test".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "test".to_string(), + )), command: vec![ "python3".to_string(), "-c".to_string(), @@ -2260,7 +2269,9 @@ mod tests { #[test] fn validate_exec_still_rejects_null_bytes_in_command_args() { let req = ExecSandboxRequest { - sandbox_id: "test".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "test".to_string(), + )), command: vec!["echo".to_string(), "hello\x00world".to_string()], ..Default::default() }; @@ -2271,7 +2282,9 @@ mod tests { #[test] fn validate_exec_still_rejects_newlines_in_workdir() { let req = ExecSandboxRequest { - sandbox_id: "test".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "test".to_string(), + )), command: vec!["ls".to_string()], workdir: "/tmp\nmalicious".to_string(), ..Default::default() @@ -2283,7 +2296,9 @@ mod tests { #[test] fn validate_exec_still_rejects_newlines_in_env_values() { let req = ExecSandboxRequest { - sandbox_id: "test".to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + "test".to_string(), + )), command: vec!["ls".to_string()], environment: std::iter::once(("VAR".to_string(), "val\nmalicious".to_string())) .collect(), diff --git a/crates/openshell-supervisor-process/src/debug_rpc.rs b/crates/openshell-supervisor-process/src/debug_rpc.rs index 6f885a69d..c1b5b8401 100644 --- a/crates/openshell-supervisor-process/src/debug_rpc.rs +++ b/crates/openshell-supervisor-process/src/debug_rpc.rs @@ -76,7 +76,7 @@ async fn run_get_sandbox_config(args: &[String]) -> Result { let mut client = open_client().await?; let resp = client .get_sandbox_config(GetSandboxConfigRequest { - sandbox_id: sandbox_id.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)), }) .await; match resp { diff --git a/crates/openshell-tui/src/lib.rs b/crates/openshell-tui/src/lib.rs index 2ab19d89d..3f48a030b 100644 --- a/crates/openshell-tui/src/lib.rs +++ b/crates/openshell-tui/src/lib.rs @@ -653,17 +653,16 @@ fn spawn_log_stream(app: &mut App, tx: mpsc::UnboundedSender) { }; let mut client = app.client.clone(); - let workspace = app.selected_sandbox_workspace(); - let handle = tokio::spawn(async move { // Phase 1: Fetch initial history via unary RPC. let req = openshell_core::proto::GetSandboxLogsRequest { - sandbox_id: sandbox_id.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox_id.clone(), + )), lines: 500, since_ms: 0, sources: vec![], min_level: String::new(), - workspace_scope: Some(named_workspace_scope(workspace)), }; match tokio::time::timeout(Duration::from_secs(5), client.get_sandbox_logs(req)).await { @@ -700,7 +699,7 @@ fn spawn_log_stream(app: &mut App, tx: mpsc::UnboundedSender) { // Phase 2: Stream live logs via WatchSandbox. let req = openshell_core::proto::WatchSandboxRequest { - id: sandbox_id, + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)), follow_status: false, follow_logs: true, follow_events: false, @@ -765,8 +764,10 @@ async fn handle_sandbox_delete(app: &mut App) { } let req = openshell_core::proto::DeleteSandboxRequest { - name: sandbox_name, - workspace_scope: Some(named_workspace_scope(app.selected_sandbox_workspace())), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + app.selected_sandbox_workspace(), + sandbox_name, + )), }; match app.client.delete_sandbox(req).await { Ok(_) => { @@ -798,8 +799,10 @@ async fn fetch_sandbox_detail(app: &mut App) { }; let req = openshell_core::proto::GetSandboxRequest { - name: sandbox_name.clone(), - workspace_scope: Some(named_workspace_scope(app.selected_sandbox_workspace())), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + app.selected_sandbox_workspace(), + sandbox_name.clone(), + )), }; // Step 1: Fetch sandbox metadata (providers, sandbox ID). @@ -828,7 +831,9 @@ async fn fetch_sandbox_detail(app: &mut App) { // Step 2: Fetch the current live policy (includes updates since creation). if let Some(id) = sandbox_id { - let policy_req = openshell_core::proto::GetSandboxConfigRequest { sandbox_id: id }; + let policy_req = openshell_core::proto::GetSandboxConfigRequest { + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(id)), + }; match tokio::time::timeout( Duration::from_secs(5), @@ -884,8 +889,10 @@ async fn handle_shell_connect( // Step 1: Get sandbox ID. let sandbox_id = { let req = openshell_core::proto::GetSandboxRequest { - name: sandbox_name.clone(), - workspace_scope: Some(named_workspace_scope(app.selected_sandbox_workspace())), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + app.selected_sandbox_workspace(), + sandbox_name.clone(), + )), }; match tokio::time::timeout(Duration::from_secs(5), app.client.get_sandbox(req)).await { Ok(Ok(resp)) => { @@ -910,7 +917,9 @@ async fn handle_shell_connect( // Step 2: Create SSH session. let session = { let req = openshell_core::proto::CreateSshSessionRequest { - sandbox_id: sandbox_id.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox_id.clone(), + )), }; match tokio::time::timeout(Duration::from_secs(5), app.client.create_ssh_session(req)).await { @@ -1041,8 +1050,10 @@ async fn handle_exec_command( // Step 1: Resolve sandbox → SSH session (same as handle_shell_connect). let sandbox_id = { let req = openshell_core::proto::GetSandboxRequest { - name: sandbox_name.to_string(), - workspace_scope: Some(named_workspace_scope(workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, + sandbox_name, + )), }; match tokio::time::timeout(Duration::from_secs(5), app.client.get_sandbox(req)).await { Ok(Ok(resp)) => { @@ -1066,7 +1077,9 @@ async fn handle_exec_command( let session = { let req = openshell_core::proto::CreateSshSessionRequest { - sandbox_id: sandbox_id.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox_id.clone(), + )), }; match tokio::time::timeout(Duration::from_secs(5), app.client.create_ssh_session(req)).await { @@ -1460,8 +1473,10 @@ fn spawn_create_sandbox(app: &mut App, tx: mpsc::UnboundedSender) { tokio::time::sleep(Duration::from_secs(2)).await; let req = openshell_core::proto::GetSandboxRequest { - name: sandbox_name.clone(), - workspace_scope: Some(named_workspace_scope(&workspace)), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + &workspace, + sandbox_name.clone(), + )), }; // Retry on transient errors. if let Ok(resp) = client.get_sandbox(req).await @@ -1519,7 +1534,7 @@ async fn start_port_forwards( // Create SSH session. let session = { let req = openshell_core::proto::CreateSshSessionRequest { - sandbox_id: sandbox_id.to_string(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)), }; match tokio::time::timeout(Duration::from_secs(10), client.create_ssh_session(req)).await { Ok(Ok(resp)) => resp.into_inner(), @@ -1894,9 +1909,10 @@ fn spawn_draft_approve(app: &App, tx: mpsc::UnboundedSender) { tokio::spawn(async move { let req = openshell_core::proto::ApproveDraftChunkRequest { - name, + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, name, + )), chunk_id, - workspace_scope: Some(named_workspace_scope(workspace)), review_token, }; match tokio::time::timeout(Duration::from_secs(5), client.approve_draft_chunk(req)).await { @@ -1939,10 +1955,11 @@ fn spawn_draft_reject(app: &App, tx: mpsc::UnboundedSender) { tokio::spawn(async move { let req = openshell_core::proto::RejectDraftChunkRequest { - name, + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, name, + )), chunk_id, reason: String::new(), - workspace_scope: Some(named_workspace_scope(workspace)), }; match tokio::time::timeout(Duration::from_secs(5), client.reject_draft_chunk(req)).await { Ok(Ok(_)) => { @@ -1989,9 +2006,10 @@ fn spawn_draft_approve_all( }) .collect(); let req = openshell_core::proto::ApproveAllDraftChunksRequest { - name, + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, name, + )), include_security_flagged: false, - workspace_scope: Some(named_workspace_scope(workspace)), approvals, }; match tokio::time::timeout( @@ -2262,11 +2280,10 @@ async fn refresh_global_settings(app: &mut App) { // Check for an active global policy only while the caller can read it. let policy_req = openshell_core::proto::ListSandboxPoliciesRequest { - name: String::new(), + sandbox_ref: None, limit: 1, offset: 0, global: true, - workspace_scope: None, }; match tokio::time::timeout( Duration::from_secs(5), @@ -2342,7 +2359,6 @@ fn spawn_set_global_setting(app: &App, tx: mpsc::UnboundedSender) { }; let req = UpdateConfigRequest { - name: String::new(), setting_key: key, setting_value: Some(SettingValue { value: Some(value) }), global: true, @@ -2375,7 +2391,6 @@ fn spawn_delete_global_setting(app: &App, tx: mpsc::UnboundedSender) { use openshell_core::proto::UpdateConfigRequest; let req = UpdateConfigRequest { - name: String::new(), setting_key: key, delete_setting: true, global: true, @@ -2443,10 +2458,11 @@ fn spawn_set_sandbox_setting(app: &App, tx: mpsc::UnboundedSender) { }; let req = UpdateConfigRequest { - name, + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, name, + )), setting_key: key, setting_value: Some(SettingValue { value: Some(value) }), - workspace_scope: Some(named_workspace_scope(workspace)), ..Default::default() }; @@ -2481,10 +2497,11 @@ fn spawn_delete_sandbox_setting(app: &App, tx: mpsc::UnboundedSender) { use openshell_core::proto::UpdateConfigRequest; let req = UpdateConfigRequest { - name, + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + workspace, name, + )), setting_key: key, delete_setting: true, - workspace_scope: Some(named_workspace_scope(workspace)), ..Default::default() }; @@ -2640,7 +2657,9 @@ async fn refresh_sandbox_policy(app: &mut App) { None => return, }; - let policy_req = openshell_core::proto::GetSandboxConfigRequest { sandbox_id }; + let policy_req = openshell_core::proto::GetSandboxConfigRequest { + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)), + }; match tokio::time::timeout( Duration::from_secs(5), @@ -2678,9 +2697,11 @@ async fn refresh_draft_chunks(app: &mut App) { }; let req = openshell_core::proto::GetDraftPolicyRequest { - name: sandbox_name, + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + app.selected_sandbox_workspace(), + sandbox_name, + )), status_filter: String::new(), - workspace_scope: Some(named_workspace_scope(app.selected_sandbox_workspace())), }; if let Ok(Ok(resp)) = @@ -2709,9 +2730,8 @@ async fn refresh_sandbox_draft_counts(app: &mut App) { .cloned() .unwrap_or_else(|| app.current_workspace.clone()); let req = openshell_core::proto::GetDraftPolicyRequest { - name: name.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(ws, name)), status_filter: "pending".to_string(), - workspace_scope: Some(named_workspace_scope(ws)), }; if let Ok(Ok(resp)) = tokio::time::timeout(Duration::from_secs(2), app.client.get_draft_policy(req)).await diff --git a/docs/sandboxes/manage-workspaces.mdx b/docs/sandboxes/manage-workspaces.mdx index 003201ac2..6608ce307 100644 --- a/docs/sandboxes/manage-workspaces.mdx +++ b/docs/sandboxes/manage-workspaces.mdx @@ -166,7 +166,9 @@ The public API represents this choice with a `WorkspaceSelector` oneof. Set `all_workspaces` marker on list requests that support it. Omitting the selector or sending an unset selector is invalid for workspace-scoped operations. The all-workspaces variant is accepted only by sandbox, sandbox template, provider, -and service list requests, and it requires Platform Admin access. +and service list requests, and it requires Platform Admin access. For services, +choose either a sandbox name or `--all-workspaces`; the options are mutually +exclusive. Clients migrating from the previous request fields should make the scope explicit: @@ -178,6 +180,21 @@ explicit: | `all_workspaces: true` | `workspace_scope.all_workspaces: {}` | | `global: true` | Omit `workspace_scope` | +Sandbox-scoped RPCs use a `SandboxReference` instead of separate sandbox and +workspace fields. Use the name variant for human-readable lookup and include a +named workspace selector. Use the ID variant when the canonical sandbox ID is +already known; ID references must omit `workspace_scope`. + +| Previous request fields | Sandbox reference | +| --- | --- | +| `name: "agent"`, `workspace_scope.workspace: "team-ml"` | `sandbox_ref.name: "agent"`, `sandbox_ref.workspace_scope.workspace: "team-ml"` | +| `sandbox_name: "agent"`, `workspace_scope.workspace: "team-ml"` | `sandbox_ref.name: "agent"`, `sandbox_ref.workspace_scope.workspace: "team-ml"` | +| `sandbox_id: ""` | `sandbox_ref.id: ""` | + +Do not set both `sandbox_ref` and a request-level `workspace_scope`. List RPCs +that are not filtered to one sandbox continue to use the request-level +`workspace_scope` selector. + The Rust and Python SDKs expose separate all-workspaces list methods. The Go SDK uses `ListAll`, and the TypeScript SDK uses a discriminated option type, so a caller cannot select a named workspace and all workspaces in one typed call. diff --git a/examples/governance-interceptor/src/main.rs b/examples/governance-interceptor/src/main.rs index eefeb695f..058cc5ae7 100644 --- a/examples/governance-interceptor/src/main.rs +++ b/examples/governance-interceptor/src/main.rs @@ -1252,11 +1252,13 @@ async fn propagate_policy_to_running_sandboxes( .map_or(0, |metadata| metadata.resource_version); let result = client .update_config(UpdateConfigRequest { - name: name.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + name.clone(), + )), policy: Some(policy_state.policy_proto.clone()), annotations: policy_update_annotations(policy_state, &correlation_id), expected_resource_version: resource_version, - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() }) .await; diff --git a/examples/governance-interceptor/src/smoke_client.rs b/examples/governance-interceptor/src/smoke_client.rs index 25d9ef2f6..19c5aa360 100644 --- a/examples/governance-interceptor/src/smoke_client.rs +++ b/examples/governance-interceptor/src/smoke_client.rs @@ -57,7 +57,9 @@ async fn main() -> Result<(), Box> { let before = client .get_sandbox_config(GetSandboxConfigRequest { - sandbox_id: sandbox_id.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id( + sandbox_id.clone(), + )), }) .await? .into_inner(); @@ -83,9 +85,11 @@ async fn main() -> Result<(), Box> { let policy_result = client .update_config(UpdateConfigRequest { - name: sandbox_name.clone(), + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name( + "default", + sandbox_name.clone(), + )), policy: Some(widened_policy), - workspace_scope: Some(openshell_core::proto::workspace_selector("default")), ..Default::default() }) .await; @@ -120,7 +124,9 @@ async fn main() -> Result<(), Box> { .map_err(|status| format!("telemetry-only policy analysis was denied: {status}"))?; let after = client - .get_sandbox_config(GetSandboxConfigRequest { sandbox_id }) + .get_sandbox_config(GetSandboxConfigRequest { + sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)), + }) .await? .into_inner(); if after.version != before.version || after.policy_hash != before.policy_hash { diff --git a/proto/datamodel.proto b/proto/datamodel.proto index d39ee7550..f837c8f6f 100644 --- a/proto/datamodel.proto +++ b/proto/datamodel.proto @@ -27,6 +27,21 @@ message WorkspaceSelector { // Marker for the all-workspaces selector variant. message AllWorkspaces {} +// Identifies one existing sandbox in the public API. +// +// Exactly one identifier is required. `id` is the immutable, globally unique +// gateway-generated ObjectMeta.id and MUST NOT include workspace_scope. `name` +// is the human-readable ObjectMeta.name and requires a named workspace_scope; +// all_workspaces is never valid for a sandbox reference. +message SandboxReference { + oneof identifier { + string id = 1; + string name = 2; + } + + WorkspaceSelector workspace_scope = 3; +} + // Kubernetes-style metadata shared by all top-level OpenShell domain objects. // // This structure provides consistent metadata (identity, labels, annotations, diff --git a/proto/openshell.proto b/proto/openshell.proto index 3477b145e..2202c9eff 100644 --- a/proto/openshell.proto +++ b/proto/openshell.proto @@ -1214,12 +1214,9 @@ message BeginRootfsTarStagingResponse { // Get sandbox request. message GetSandboxRequest { - reserved 2; - reserved "workspace"; - // Sandbox name (canonical lookup key). - string name = 1; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3; + reserved 1, 2, 3; + reserved "name", "workspace", "workspace_scope"; + openshell.datamodel.v1.SandboxReference sandbox_ref = 4; } // List sandboxes request. @@ -1236,20 +1233,15 @@ message ListSandboxesRequest { // List providers attached to a sandbox request. message ListSandboxProvidersRequest { - reserved 2; - reserved "workspace"; - // Sandbox name (canonical lookup key). - string sandbox_name = 1; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3; + reserved 1, 2, 3; + reserved "sandbox_name", "workspace", "workspace_scope"; + openshell.datamodel.v1.SandboxReference sandbox_ref = 4; } // Attach provider to sandbox request. message AttachSandboxProviderRequest { - reserved 4; - reserved "workspace"; - // Sandbox name (canonical lookup key). - string sandbox_name = 1; + reserved 1, 4, 5; + reserved "sandbox_name", "workspace", "workspace_scope"; // Provider name to attach. string provider_name = 2; // Expected resource version for optimistic concurrency control. @@ -1257,16 +1249,13 @@ message AttachSandboxProviderRequest { // If non-zero, the server validates that the sandbox's current resource_version // matches this value before applying the mutation, returning ABORTED on mismatch. uint64 expected_resource_version = 3; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5; + openshell.datamodel.v1.SandboxReference sandbox_ref = 6; } // Detach provider from sandbox request. message DetachSandboxProviderRequest { - reserved 4; - reserved "workspace"; - // Sandbox name (canonical lookup key). - string sandbox_name = 1; + reserved 1, 4, 5; + reserved "sandbox_name", "workspace", "workspace_scope"; // Provider name to detach. string provider_name = 2; // Expected resource version for optimistic concurrency control. @@ -1274,38 +1263,28 @@ message DetachSandboxProviderRequest { // If non-zero, the server validates that the sandbox's current resource_version // matches this value before applying the mutation, returning ABORTED on mismatch. uint64 expected_resource_version = 3; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5; + openshell.datamodel.v1.SandboxReference sandbox_ref = 6; } // Delete sandbox request. message DeleteSandboxRequest { - reserved 2; - reserved "workspace"; - // Sandbox name (canonical lookup key). - string name = 1; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3; + reserved 1, 2, 3; + reserved "name", "workspace", "workspace_scope"; + openshell.datamodel.v1.SandboxReference sandbox_ref = 4; } // Stop sandbox request. message StopSandboxRequest { - reserved 2; - reserved "workspace"; - // Sandbox name (canonical lookup key). - string name = 1; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3; + reserved 1, 2, 3; + reserved "name", "workspace", "workspace_scope"; + openshell.datamodel.v1.SandboxReference sandbox_ref = 4; } // Start sandbox request. message StartSandboxRequest { - reserved 2; - reserved "workspace"; - // Sandbox name (canonical lookup key). - string name = 1; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3; + reserved 1, 2, 3; + reserved "name", "workspace", "workspace_scope"; + openshell.datamodel.v1.SandboxReference sandbox_ref = 4; } // Sandbox response. @@ -1344,8 +1323,9 @@ message DeleteSandboxResponse { // Create SSH session request. message CreateSshSessionRequest { - // Sandbox id. - string sandbox_id = 1; + reserved 1; + reserved "sandbox_id"; + openshell.datamodel.v1.SandboxReference sandbox_ref = 2; } // Create SSH session response. @@ -1384,44 +1364,40 @@ message CreateSshSessionResponse { // Request to expose an HTTP service running inside a sandbox. message ExposeServiceRequest { - reserved 5; - reserved "workspace"; - // Sandbox name. - string sandbox = 1; + reserved 1, 5, 6; + reserved "sandbox", "workspace", "workspace_scope"; // Service name within the sandbox. string service = 2; // Loopback TCP port inside the sandbox. uint32 target_port = 3; // Whether to print/use the browser-facing service URL. bool domain = 4; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 6; + openshell.datamodel.v1.SandboxReference sandbox_ref = 7; } // Request to fetch an exposed sandbox service endpoint. message GetServiceRequest { - reserved 3; - reserved "workspace"; - // Sandbox name. - string sandbox = 1; + reserved 1, 3, 4; + reserved "sandbox", "workspace", "workspace_scope"; // Service name within the sandbox. Empty selects the unnamed endpoint. string service = 2; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4; + openshell.datamodel.v1.SandboxReference sandbox_ref = 5; } // Request to list exposed sandbox service endpoints. message ListServicesRequest { reserved 4, 5; reserved "workspace", "all_workspaces"; - // Optional sandbox name. Empty lists endpoints for all sandboxes. - string sandbox = 1; + reserved 1; + reserved "sandbox"; // Page size. Zero uses the server default. uint32 limit = 2; // Page offset. uint32 offset = 3; // Explicit named or all-workspaces scope. openshell.datamodel.v1.WorkspaceSelector workspace_scope = 6; + // Optional sandbox filter. When present, workspace_scope must be omitted. + openshell.datamodel.v1.SandboxReference sandbox_ref = 7; } // Response containing exposed sandbox service endpoints. @@ -1431,14 +1407,11 @@ message ListServicesResponse { // Request to delete an exposed sandbox service endpoint. message DeleteServiceRequest { - reserved 3; - reserved "workspace"; - // Sandbox name. - string sandbox = 1; + reserved 1, 3, 4; + reserved "sandbox", "workspace", "workspace_scope"; // Service name within the sandbox. Empty selects the unnamed endpoint. string service = 2; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4; + openshell.datamodel.v1.SandboxReference sandbox_ref = 5; } // Response for deleting an exposed sandbox service endpoint. @@ -1483,8 +1456,8 @@ message RevokeSshSessionResponse { // Execute command request. message ExecSandboxRequest { - // Sandbox id. - string sandbox_id = 1; + reserved 1; + reserved "sandbox_id"; // Command and arguments. repeated string command = 2; @@ -1516,6 +1489,7 @@ message ExecSandboxRequest { // sourced by them) are applied. When true, the command runs without those // files (`bash -c`), for automation that needs predictable startup behavior. bool no_login_shell = 10; + openshell.datamodel.v1.SandboxReference sandbox_ref = 11; } // One stdout chunk from a sandbox exec. @@ -1544,8 +1518,9 @@ message ExecSandboxEvent { // Initial frame for one TCP forward stream. message TcpForwardInit { - // Sandbox id. - string sandbox_id = 1; + reserved 1; + reserved "sandbox_id"; + openshell.datamodel.v1.SandboxReference sandbox_ref = 2; // Optional service identifier for audit/correlation. string service_id = 4; // Target the gateway should request from the supervisor. @@ -1606,8 +1581,8 @@ message SshSession { // Watch sandbox request. message WatchSandboxRequest { - // Sandbox id. - string id = 1; + reserved 1; + reserved "id"; // Stream sandbox status snapshots. bool follow_status = 2; @@ -1637,6 +1612,7 @@ message WatchSandboxRequest { // Minimum log level to include (e.g. "INFO", "WARN", "ERROR"). Empty means all levels. string log_min_level = 10; + openshell.datamodel.v1.SandboxReference sandbox_ref = 11; } // One event in a sandbox watch stream. @@ -2241,11 +2217,8 @@ message ExchangeProviderSubjectTokenResponse { // Update sandbox policy request. message UpdateConfigRequest { - reserved 10; - reserved "workspace"; - // Sandbox name (canonical lookup key). Required for sandbox-scoped updates. - // Not required when `global=true`. - string name = 1; + reserved 1, 10, 11; + reserved "name", "workspace", "workspace_scope"; // The new policy to apply. // // Sandbox scope (`global=false`): @@ -2279,9 +2252,8 @@ message UpdateConfigRequest { // sandbox metadata as a convenience projection. For setting-only updates, it // only merges them into sandbox metadata. map annotations = 9; - // Explicit workspace scope for sandbox-scoped updates. Omit only when - // `global` is true; the all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 11; + // Required for sandbox-scoped updates and omitted for global updates. + openshell.datamodel.v1.SandboxReference sandbox_ref = 12; } message PolicyMergeOperation { @@ -2343,17 +2315,14 @@ message UpdateConfigResponse { // Get sandbox policy status request. message GetSandboxPolicyStatusRequest { - reserved 4; - reserved "workspace"; - // Sandbox name (canonical lookup key). Ignored when global is true. - string name = 1; + reserved 1, 4, 5; + reserved "name", "workspace", "workspace_scope"; // The specific policy version to query. 0 means latest. uint32 version = 2; // Query global policy revisions instead of a sandbox-scoped one. bool global = 3; - // Explicit workspace scope for sandbox-scoped queries. Omit only when - // `global` is true; the all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5; + // Required for sandbox-scoped queries and omitted for global queries. + openshell.datamodel.v1.SandboxReference sandbox_ref = 6; } // Get sandbox policy status response. @@ -2366,17 +2335,14 @@ message GetSandboxPolicyStatusResponse { // List sandbox policies request. message ListSandboxPoliciesRequest { - reserved 5; - reserved "workspace"; - // Sandbox name (canonical lookup key). Ignored when global is true. - string name = 1; + reserved 1, 5, 6; + reserved "name", "workspace", "workspace_scope"; uint32 limit = 2; uint32 offset = 3; // List global policy revisions instead of sandbox-scoped ones. bool global = 4; - // Explicit workspace scope for sandbox-scoped queries. Omit only when - // `global` is true; the all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 6; + // Required for sandbox-scoped queries and omitted for global queries. + openshell.datamodel.v1.SandboxReference sandbox_ref = 7; } // List sandbox policies response. @@ -2447,10 +2413,8 @@ enum PolicyStatus { // Get sandbox logs request (one-shot fetch). message GetSandboxLogsRequest { - reserved 6; - reserved "workspace"; - // Sandbox id. - string sandbox_id = 1; + reserved 1, 6, 7; + reserved "sandbox_id", "workspace", "workspace_scope"; // Maximum number of log lines to return. 0 means use default (2000). uint32 lines = 2; // Only include logs with timestamp >= this value (ms since epoch). 0 means no filter. @@ -2459,8 +2423,7 @@ message GetSandboxLogsRequest { repeated string sources = 4; // Minimum log level to include (e.g. "INFO", "WARN", "ERROR"). Empty means all levels. string min_level = 5; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 7; + openshell.datamodel.v1.SandboxReference sandbox_ref = 8; } // Batch of log lines pushed from sandbox to server. @@ -2790,11 +2753,14 @@ message SubmitPolicyAnalysisRequest { // to watch. Other values are treated as agent-style (no dedup) so a new // mode does not silently collapse proposals. string analysis_mode = 3; - // Sandbox name. + // Internal sandbox callback name. This request is not part of the public + // SandboxReference contract; the authenticated sandbox principal is the + // authoritative identity. string name = 4; // Anonymous network activity counters. repeated NetworkActivitySummary network_activity_summaries = 5; - // Workspace scope. Empty defaults to "default". + // Internal callback workspace. The gateway validates it against the + // authenticated sandbox principal. string workspace = 6; } @@ -2813,14 +2779,11 @@ message SubmitPolicyAnalysisResponse { // Get draft policy for a sandbox. message GetDraftPolicyRequest { - reserved 3; - reserved "workspace"; - // Sandbox name. - string name = 1; + reserved 1, 3, 4; + reserved "name", "workspace", "workspace_scope"; // Optional status filter: "pending", "approved", "rejected", or "" for all. string status_filter = 2; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4; + openshell.datamodel.v1.SandboxReference sandbox_ref = 5; } message GetDraftPolicyResponse { @@ -2836,17 +2799,14 @@ message GetDraftPolicyResponse { // Approve a single draft chunk. message ApproveDraftChunkRequest { - reserved 3; - reserved "workspace"; - // Sandbox name. - string name = 1; + reserved 1, 3, 5; + reserved "name", "workspace", "workspace_scope"; // Chunk ID to approve. string chunk_id = 2; // Token returned with the reviewed PolicyChunk. Approval fails with // FAILED_PRECONDITION if live decision inputs no longer match it. string review_token = 4; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5; + openshell.datamodel.v1.SandboxReference sandbox_ref = 6; } message ApproveDraftChunkResponse { @@ -2858,16 +2818,13 @@ message ApproveDraftChunkResponse { // Reject a single draft chunk. message RejectDraftChunkRequest { - reserved 4; - reserved "workspace"; - // Sandbox name. - string name = 1; + reserved 1, 4, 5; + reserved "name", "workspace", "workspace_scope"; // Chunk ID to reject. string chunk_id = 2; // Optional reason for rejection (fed to LLM context in future analysis). string reason = 3; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5; + openshell.datamodel.v1.SandboxReference sandbox_ref = 6; } message RejectDraftChunkResponse {} @@ -2879,17 +2836,14 @@ message DraftChunkApproval { } message ApproveAllDraftChunksRequest { - reserved 3; - reserved "workspace"; - // Sandbox name. - string name = 1; + reserved 1, 3, 5; + reserved "name", "workspace", "workspace_scope"; // Include chunks with security_notes (default false: skips them). bool include_security_flagged = 2; // Exact reviewed chunks and tokens. The server validates them against one // live snapshot, stages compatible operations in order, and writes once. repeated DraftChunkApproval approvals = 4; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5; + openshell.datamodel.v1.SandboxReference sandbox_ref = 6; } message ApproveAllDraftChunksResponse { @@ -2906,30 +2860,24 @@ message ApproveAllDraftChunksResponse { // Edit a pending chunk in-place. message EditDraftChunkRequest { - reserved 4; - reserved "workspace"; - // Sandbox name. - string name = 1; + reserved 1, 4, 5; + reserved "name", "workspace", "workspace_scope"; // Chunk ID to edit. string chunk_id = 2; // The modified rule (replaces existing proposed_rule). openshell.sandbox.v1.NetworkPolicyRule proposed_rule = 3; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5; + openshell.datamodel.v1.SandboxReference sandbox_ref = 6; } message EditDraftChunkResponse {} // Reverse an approval (remove merged rule from active policy). message UndoDraftChunkRequest { - reserved 3; - reserved "workspace"; - // Sandbox name. - string name = 1; + reserved 1, 3, 4; + reserved "name", "workspace", "workspace_scope"; // Chunk ID to undo. string chunk_id = 2; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4; + openshell.datamodel.v1.SandboxReference sandbox_ref = 5; } message UndoDraftChunkResponse { @@ -2941,12 +2889,9 @@ message UndoDraftChunkResponse { // Clear all pending draft chunks for a sandbox. message ClearDraftChunksRequest { - reserved 2; - reserved "workspace"; - // Sandbox name. - string name = 1; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3; + reserved 1, 2, 3; + reserved "name", "workspace", "workspace_scope"; + openshell.datamodel.v1.SandboxReference sandbox_ref = 4; } message ClearDraftChunksResponse { @@ -2956,12 +2901,9 @@ message ClearDraftChunksResponse { // Get decision history for a sandbox's draft policy. message GetDraftHistoryRequest { - reserved 2; - reserved "workspace"; - // Sandbox name. - string name = 1; - // Explicit workspace scope. The all-workspaces selection is invalid. - openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3; + reserved 1, 2, 3; + reserved "name", "workspace", "workspace_scope"; + openshell.datamodel.v1.SandboxReference sandbox_ref = 4; } message DraftHistoryEntry { diff --git a/proto/sandbox.proto b/proto/sandbox.proto index c2b61d0b3..5ca00b5e3 100644 --- a/proto/sandbox.proto +++ b/proto/sandbox.proto @@ -5,6 +5,7 @@ syntax = "proto3"; package openshell.sandbox.v1; +import "datamodel.proto"; import "google/protobuf/struct.proto"; // Sandbox-supervisor configuration and policy messages. @@ -317,8 +318,11 @@ message NetworkBinary { // Request to get sandbox settings by sandbox ID. message GetSandboxConfigRequest { - // The sandbox ID. - string sandbox_id = 1; + reserved 1; + reserved "sandbox_id"; + // Canonical public sandbox reference. Sandbox-authenticated callers use the + // ID variant; user clients may use either supported variant. + openshell.datamodel.v1.SandboxReference sandbox_ref = 2; } // Request to get gateway-global settings. diff --git a/python/openshell/sandbox.py b/python/openshell/sandbox.py index 343d75edc..88da47219 100644 --- a/python/openshell/sandbox.py +++ b/python/openshell/sandbox.py @@ -47,6 +47,18 @@ def _all_workspaces_scope() -> datamodel_pb2.WorkspaceSelector: return datamodel_pb2.WorkspaceSelector(all_workspaces=datamodel_pb2.AllWorkspaces()) +def _sandbox_reference_by_name( + workspace: str, name: str +) -> datamodel_pb2.SandboxReference: + return datamodel_pb2.SandboxReference( + name=name, workspace_scope=_workspace_scope(workspace) + ) + + +def _sandbox_reference_by_id(sandbox_id: str) -> datamodel_pb2.SandboxReference: + return datamodel_pb2.SandboxReference(id=sandbox_id) + + class _ClientCallDetails(_ClientCallDetailsBase, grpc.ClientCallDetails): pass @@ -806,7 +818,7 @@ class SandboxClient: def get(self, sandbox_name: str, *, workspace: str) -> SandboxRef: response = self._stub.GetSandbox( openshell_pb2.GetSandboxRequest( - name=sandbox_name, workspace_scope=_workspace_scope(workspace) + sandbox_ref=_sandbox_reference_by_name(workspace, sandbox_name) ), timeout=self._timeout, ) @@ -885,7 +897,7 @@ class SandboxClient: def delete(self, sandbox_name: str, *, workspace: str) -> bool: response = self._stub.DeleteSandbox( openshell_pb2.DeleteSandboxRequest( - name=sandbox_name, workspace_scope=_workspace_scope(workspace) + sandbox_ref=_sandbox_reference_by_name(workspace, sandbox_name) ), timeout=self._timeout, ) @@ -894,7 +906,7 @@ class SandboxClient: def stop(self, sandbox_name: str, *, workspace: str) -> SandboxRef: response = self._stub.StopSandbox( openshell_pb2.StopSandboxRequest( - name=sandbox_name, workspace_scope=_workspace_scope(workspace) + sandbox_ref=_sandbox_reference_by_name(workspace, sandbox_name) ), timeout=self._timeout, ) @@ -903,7 +915,7 @@ class SandboxClient: def start(self, sandbox_name: str, *, workspace: str) -> SandboxRef: response = self._stub.StartSandbox( openshell_pb2.StartSandboxRequest( - name=sandbox_name, workspace_scope=_workspace_scope(workspace) + sandbox_ref=_sandbox_reference_by_name(workspace, sandbox_name) ), timeout=self._timeout, ) @@ -996,7 +1008,7 @@ class SandboxClient: raise SandboxError("command must not be empty") request = openshell_pb2.ExecSandboxRequest( - sandbox_id=sandbox_id, + sandbox_ref=_sandbox_reference_by_id(sandbox_id), command=list(command), workdir=workdir or "", environment=dict(env or {}), diff --git a/python/openshell/sandbox_test.py b/python/openshell/sandbox_test.py index 772a32723..79ec4e876 100644 --- a/python/openshell/sandbox_test.py +++ b/python/openshell/sandbox_test.py @@ -43,7 +43,12 @@ from openshell.sandbox import ( def _request_workspace(request: Any) -> str | None: - scope = request.workspace_scope + sandbox_ref = getattr(request, "sandbox_ref", None) + scope = ( + sandbox_ref.workspace_scope + if sandbox_ref is not None and sandbox_ref.HasField("workspace_scope") + else request.workspace_scope + ) if scope.WhichOneof("selection") == "workspace": return cast("str", scope.workspace) return None @@ -53,6 +58,12 @@ def _request_selects_all_workspaces(request: Any) -> bool: return request.workspace_scope.WhichOneof("selection") == "all_workspaces" +def _request_sandbox_name(request: Any) -> str: + sandbox_ref = request.sandbox_ref + assert sandbox_ref.WhichOneof("identifier") == "name" + return cast("str", sandbox_ref.name) + + def _client_credentials_fixture() -> dict[str, Any]: return json.loads( ( @@ -1993,7 +2004,7 @@ class _FakeSandboxStub: return SimpleNamespace( sandbox=_make_sandbox_proto( "sandbox-1", - request.name, + _request_sandbox_name(request), workspace=_request_workspace(request) or "default", ) ) @@ -2017,7 +2028,7 @@ class _FakeSandboxStub: return SimpleNamespace( sandbox=_make_sandbox_proto( "sandbox-1", - request.name, + _request_sandbox_name(request), phase=openshell_pb2.SANDBOX_PHASE_STOPPED, workspace=_request_workspace(request) or "default", ) @@ -2033,7 +2044,7 @@ class _FakeSandboxStub: return SimpleNamespace( sandbox=_make_sandbox_proto( "sandbox-1", - request.name, + _request_sandbox_name(request), phase=openshell_pb2.SANDBOX_PHASE_STARTING, workspace=_request_workspace(request) or "default", ) @@ -2429,13 +2440,13 @@ def test_stop_and_start_forward_workspace_and_return_phase() -> None: stopped = client.stop("job-1", workspace="team-a") assert stub.stop_request is not None - assert stub.stop_request.name == "job-1" + assert _request_sandbox_name(stub.stop_request) == "job-1" assert _request_workspace(stub.stop_request) == "team-a" assert stopped.phase == openshell_pb2.SANDBOX_PHASE_STOPPED starting = client.start("job-1", workspace="team-a") assert stub.start_request is not None - assert stub.start_request.name == "job-1" + assert _request_sandbox_name(stub.start_request) == "job-1" assert _request_workspace(stub.start_request) == "team-a" assert starting.phase == openshell_pb2.SANDBOX_PHASE_STARTING @@ -2460,7 +2471,7 @@ def test_wait_ready_handles_terminal_main_process_results( return SimpleNamespace( sandbox=_make_sandbox_proto( "sandbox-1", - request.name, + _request_sandbox_name(request), phase=phase, workspace=_request_workspace(request) or "default", ) diff --git a/sdk/go/openshell/v1/config_client.go b/sdk/go/openshell/v1/config_client.go index b2cb3fd37..4a5140952 100644 --- a/sdk/go/openshell/v1/config_client.go +++ b/sdk/go/openshell/v1/config_client.go @@ -31,7 +31,7 @@ func (c *configClient) GetSandbox(ctx context.Context, workspace, sandboxName st } resp, err := c.client.GetSandboxConfig(ctx, &sbv1.GetSandboxConfigRequest{ - SandboxId: sb.ID, + SandboxRef: sandboxReferenceByID(sb.ID), }) if err != nil { return nil, converter.FromGRPCError(err) @@ -59,7 +59,7 @@ func (c *configClient) Update(ctx context.Context, workspace string, update *Con return nil, &StatusError{Code: ErrorInvalidArgument, Message: convErr.Error()} } if !req.GetGlobal() { - req.WorkspaceScope = namedWorkspaceScope(workspace) + req.SandboxRef.WorkspaceScope = namedWorkspaceScope(workspace) } resp, err := c.client.UpdateConfig(ctx, req) if err != nil { diff --git a/sdk/go/openshell/v1/config_client_test.go b/sdk/go/openshell/v1/config_client_test.go index 9fa8675ff..a2e1defbe 100644 --- a/sdk/go/openshell/v1/config_client_test.go +++ b/sdk/go/openshell/v1/config_client_test.go @@ -143,7 +143,7 @@ func TestConfigGetSandbox(t *testing.T) { // Verify request was forwarded with resolved ID (stubSandboxResolver returns "sb-"). mock.mu.Lock() - assert.Equal(t, "sb-my-sandbox", mock.lastSandboxReq.GetSandboxId()) + assert.Equal(t, "sb-my-sandbox", mock.lastSandboxReq.GetSandboxRef().GetId()) mock.mu.Unlock() // Scalar fields. @@ -235,7 +235,7 @@ func TestConfigGetSandbox_ResolvesNameToID(t *testing.T) { // stubSandboxResolver returns ID "sb-" — verify the proto has the resolved ID, not the name. mock.mu.Lock() - assert.Equal(t, "sb-my-sandbox", mock.lastSandboxReq.GetSandboxId(), "GetSandbox should send resolved sandbox ID, not the name") + assert.Equal(t, "sb-my-sandbox", mock.lastSandboxReq.GetSandboxRef().GetId(), "GetSandbox should send resolved sandbox ID, not the name") mock.mu.Unlock() } @@ -361,7 +361,7 @@ func TestConfigUpdate_SandboxScope(t *testing.T) { mock.mu.Unlock() require.NotNil(t, req) - assert.Equal(t, "my-sandbox", req.GetName()) + assert.Equal(t, "my-sandbox", req.GetSandboxRef().GetName()) assert.Equal(t, "max_tokens", req.GetSettingKey()) assert.False(t, req.GetGlobal()) assert.Equal(t, uint64(4), req.GetExpectedResourceVersion()) @@ -397,7 +397,7 @@ func TestConfigUpdate_GlobalScope(t *testing.T) { mock.mu.Unlock() assert.True(t, req.GetGlobal()) - assert.Empty(t, req.GetName()) + assert.Nil(t, req.GetSandboxRef()) } func TestConfigUpdate_DeleteSetting(t *testing.T) { diff --git a/sdk/go/openshell/v1/doc.go b/sdk/go/openshell/v1/doc.go index 900d86c64..2732347f0 100644 --- a/sdk/go/openshell/v1/doc.go +++ b/sdk/go/openshell/v1/doc.go @@ -314,7 +314,7 @@ // // Read a policy back from revision history: // -// revisions, err := client.Policy().List(ctx, "default") +// revisions, err := client.Policy().List(ctx, "default", "my-sandbox") // if err != nil { // log.Fatal(err) // } @@ -328,7 +328,7 @@ // // List gateway-global policy revisions (no sandbox name or workspace needed): // -// revisions, err := client.Policy().List(ctx, "", v1.WithListGlobal(true)) +// revisions, err := client.Policy().List(ctx, "", "", v1.WithListGlobal(true)) // if err != nil { // log.Fatal(err) // } diff --git a/sdk/go/openshell/v1/exec_client_test.go b/sdk/go/openshell/v1/exec_client_test.go index 53acc1e8b..6f3f83fa6 100644 --- a/sdk/go/openshell/v1/exec_client_test.go +++ b/sdk/go/openshell/v1/exec_client_test.go @@ -236,7 +236,7 @@ func TestExecRun_WithOptions(t *testing.T) { mock.mu.Lock() defer mock.mu.Unlock() - assert.Equal(t, "sb-test-sandbox", mock.lastExecRequest.GetSandboxId()) + assert.Equal(t, "sb-test-sandbox", mock.lastExecRequest.GetSandboxRef().GetId()) assert.Equal(t, []string{"ls"}, mock.lastExecRequest.GetCommand()) assert.Equal(t, "/tmp", mock.lastExecRequest.GetWorkdir()) assert.Equal(t, map[string]string{"FOO": "bar"}, mock.lastExecRequest.GetEnvironment()) @@ -380,7 +380,7 @@ func TestExecInteractive(t *testing.T) { startReq := startInput.GetStart() require.NotNil(t, startReq) - assert.Equal(t, "sb-test-sandbox", startReq.GetSandboxId()) + assert.Equal(t, "sb-test-sandbox", startReq.GetSandboxRef().GetId()) assert.Equal(t, []string{"/bin/bash"}, startReq.GetCommand()) assert.True(t, startReq.GetTty()) assert.Equal(t, uint32(80), startReq.GetCols()) @@ -546,7 +546,7 @@ func TestExecRun_ResolvesNameToID(t *testing.T) { mock.mu.Lock() defer mock.mu.Unlock() // Verify the proto request contains the resolved ID, not the name - assert.Equal(t, "sb-my-sandbox", mock.lastExecRequest.GetSandboxId()) + assert.Equal(t, "sb-my-sandbox", mock.lastExecRequest.GetSandboxRef().GetId()) } func TestExecRun_ResolutionError(t *testing.T) { @@ -576,7 +576,7 @@ func TestExecStream_ResolvesNameToID(t *testing.T) { mock.mu.Lock() defer mock.mu.Unlock() - assert.Equal(t, "sb-my-sandbox", mock.lastExecRequest.GetSandboxId()) + assert.Equal(t, "sb-my-sandbox", mock.lastExecRequest.GetSandboxRef().GetId()) } func TestExecStream_ResolutionError(t *testing.T) { @@ -609,7 +609,7 @@ func TestExecInteractive_ResolvesNameToID(t *testing.T) { require.NotEmpty(t, mock.receivedInputs) startReq := mock.receivedInputs[0].GetStart() require.NotNil(t, startReq) - assert.Equal(t, "sb-my-sandbox", startReq.GetSandboxId()) + assert.Equal(t, "sb-my-sandbox", startReq.GetSandboxRef().GetId()) } func TestExecInteractive_ResolutionError(t *testing.T) { diff --git a/sdk/go/openshell/v1/fake/policy.go b/sdk/go/openshell/v1/fake/policy.go index 7360af1b0..851b810bb 100644 --- a/sdk/go/openshell/v1/fake/policy.go +++ b/sdk/go/openshell/v1/fake/policy.go @@ -7,7 +7,6 @@ import ( "context" "maps" "slices" - "strings" "sync" v1 "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1" @@ -172,9 +171,8 @@ func (c *fakePolicyClient) GetStatus(_ context.Context, workspace, sandboxName s } // List returns policy revisions. When the global option is set, it returns -// global revisions; otherwise it returns all sandbox-scoped revisions for the -// given workspace. -func (c *fakePolicyClient) List(_ context.Context, workspace string, opts ...v1.ListPolicyOption) ([]types.SandboxPolicyRevision, error) { +// global revisions; otherwise it returns revisions for the specified sandbox. +func (c *fakePolicyClient) List(_ context.Context, workspace, sandboxName string, opts ...v1.ListPolicyOption) ([]types.SandboxPolicyRevision, error) { if c.closedFunc() { return nil, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"} } @@ -187,13 +185,7 @@ func (c *fakePolicyClient) List(_ context.Context, workspace string, opts ...v1. if cfg.Global() { revisions = slices.Clone(c.globalRevisions) } else { - // Collect all revisions for sandboxes in this workspace. - prefix := workspace + "/" - for key, revs := range c.sandboxRevisions { - if strings.HasPrefix(key, prefix) { - revisions = append(revisions, revs...) - } - } + revisions = slices.Clone(c.sandboxRevisions[workspace+"/"+sandboxName]) } if len(revisions) == 0 { diff --git a/sdk/go/openshell/v1/fake/policy_test.go b/sdk/go/openshell/v1/fake/policy_test.go index f48398a13..79e8b06e2 100644 --- a/sdk/go/openshell/v1/fake/policy_test.go +++ b/sdk/go/openshell/v1/fake/policy_test.go @@ -66,7 +66,7 @@ func TestFakePolicy_GetStatus_EmptyReturnsNotFound(t *testing.T) { func TestFakePolicy_List_EmptyReturnsNil(t *testing.T) { c := newFakePolicyClient(func() bool { return false }) - revisions, err := c.List(context.Background(), "default") + revisions, err := c.List(context.Background(), "default", "sb-1") require.NoError(t, err) assert.Nil(t, revisions) } @@ -96,7 +96,7 @@ func TestFakePolicy_List_Global(t *testing.T) { c.AddRevision("default", "sb-1", types.SandboxPolicyRevision{Version: 1, PolicyHash: "sha256:sb-v1", Status: types.PolicyLoadStatusLoaded}) // List global revisions. - revisions, err := c.List(context.Background(), "", types.WithListGlobal(true)) + revisions, err := c.List(context.Background(), "", "", types.WithListGlobal(true)) require.NoError(t, err) require.Len(t, revisions, 2) assert.Equal(t, uint32(1), revisions[0].Version) @@ -113,7 +113,7 @@ func TestFakePolicy_List_Sandbox(t *testing.T) { c.AddRevision("default", "sb-1", types.SandboxPolicyRevision{Version: 2, PolicyHash: "sha256:sb-v2", Status: types.PolicyLoadStatusPending}) // List sandbox-scoped revisions (no global flag). - revisions, err := c.List(context.Background(), "default") + revisions, err := c.List(context.Background(), "default", "sb-1") require.NoError(t, err) require.Len(t, revisions, 2) assert.Equal(t, "sha256:sb-v1", revisions[0].PolicyHash) @@ -127,7 +127,7 @@ func TestFakePolicy_List_NoIsolationCrossContamination(t *testing.T) { c.AddRevision("default", "sb-1", types.SandboxPolicyRevision{Version: 1, PolicyHash: "sha256:sb-v1"}) // Global list returns empty (no global revisions seeded). - revisions, err := c.List(context.Background(), "", types.WithListGlobal(true)) + revisions, err := c.List(context.Background(), "", "", types.WithListGlobal(true)) require.NoError(t, err) assert.Nil(t, revisions) } @@ -140,14 +140,14 @@ func TestFakePolicy_List_GlobalWithPagination(t *testing.T) { c.AddGlobalRevision(types.SandboxPolicyRevision{Version: 3}) // Limit to 2. - revisions, err := c.List(context.Background(), "", types.WithListGlobal(true), types.WithLimit(2)) + revisions, err := c.List(context.Background(), "", "", types.WithListGlobal(true), types.WithLimit(2)) require.NoError(t, err) require.Len(t, revisions, 2) assert.Equal(t, uint32(1), revisions[0].Version) assert.Equal(t, uint32(2), revisions[1].Version) // Offset by 1, limit 2. - revisions, err = c.List(context.Background(), "", types.WithListGlobal(true), types.WithLimit(2), types.WithOffset(1)) + revisions, err = c.List(context.Background(), "", "", types.WithListGlobal(true), types.WithLimit(2), types.WithOffset(1)) require.NoError(t, err) require.Len(t, revisions, 2) assert.Equal(t, uint32(2), revisions[0].Version) @@ -236,14 +236,14 @@ func TestFakePolicy_DeepCopyWithPolicy(t *testing.T) { ctx := context.Background() // Get global revision and mutate it. - revisions, err := fc.Policy().List(ctx, "", types.WithListGlobal(true)) + revisions, err := fc.Policy().List(ctx, "", "", types.WithListGlobal(true)) require.NoError(t, err) require.Len(t, revisions, 1) require.NotNil(t, revisions[0].Policy) revisions[0].Policy.NetworkPolicies["rule-1"] = types.NetworkPolicyRule{Name: "mutated"} // Verify internal state is not corrupted. - revisions2, err := fc.Policy().List(ctx, "", types.WithListGlobal(true)) + revisions2, err := fc.Policy().List(ctx, "", "", types.WithListGlobal(true)) require.NoError(t, err) assert.Equal(t, "rule-1", revisions2[0].Policy.NetworkPolicies["rule-1"].Name) @@ -256,7 +256,7 @@ func TestFakePolicy_DeepCopyWithPolicy(t *testing.T) { func TestFakePolicy_List_ClosedReturnsUnavailable(t *testing.T) { c := newFakePolicyClient(func() bool { return true }) - _, err := c.List(context.Background(), "", types.WithListGlobal(true)) + _, err := c.List(context.Background(), "", "", types.WithListGlobal(true)) require.Error(t, err) assert.True(t, types.IsUnavailable(err)) } diff --git a/sdk/go/openshell/v1/file_client.go b/sdk/go/openshell/v1/file_client.go index 7e9edad17..6f461ebee 100644 --- a/sdk/go/openshell/v1/file_client.go +++ b/sdk/go/openshell/v1/file_client.go @@ -59,7 +59,7 @@ func (f *fileClient) Upload(ctx context.Context, workspace, sandboxName string, } session, err := f.client.CreateSshSession(ctx, &pb.CreateSshSessionRequest{ - SandboxId: sb.ID, + SandboxRef: sandboxReferenceByID(sb.ID), }) if err != nil { return converter.FromGRPCError(err) @@ -93,7 +93,7 @@ func (f *fileClient) Download(ctx context.Context, workspace, sandboxName string } session, err := f.client.CreateSshSession(ctx, &pb.CreateSshSessionRequest{ - SandboxId: sb.ID, + SandboxRef: sandboxReferenceByID(sb.ID), }) if err != nil { return converter.FromGRPCError(err) diff --git a/sdk/go/openshell/v1/file_client_test.go b/sdk/go/openshell/v1/file_client_test.go index ac08c8dec..d34ee9f31 100644 --- a/sdk/go/openshell/v1/file_client_test.go +++ b/sdk/go/openshell/v1/file_client_test.go @@ -109,7 +109,7 @@ func TestFileUpload(t *testing.T) { err := client.Upload(context.Background(), "default", "test-sandbox", localPath, "/remote/upload.txt") require.NoError(t, err) - assert.Equal(t, "sb-test-sandbox", mock.lastCreateReq.GetSandboxId()) + assert.Equal(t, "sb-test-sandbox", mock.lastCreateReq.GetSandboxRef().GetId()) assert.Equal(t, 1, mock.createCallCount) } @@ -164,7 +164,7 @@ func TestFileDownload(t *testing.T) { err := client.Download(context.Background(), "default", "test-sandbox", "/remote/file.txt", localPath) require.NoError(t, err) - assert.Equal(t, "sb-test-sandbox", mock.lastCreateReq.GetSandboxId()) + assert.Equal(t, "sb-test-sandbox", mock.lastCreateReq.GetSandboxRef().GetId()) assert.Equal(t, 1, mock.createCallCount) } @@ -261,7 +261,7 @@ func TestFileUpload_ResolvesNameToID(t *testing.T) { _ = client.Upload(context.Background(), "default", "my-sandbox", localPath, "/remote/file.txt") // Verify the proto request contains the resolved ID, not the name - assert.Equal(t, "sb-my-sandbox", mock.lastCreateReq.GetSandboxId()) + assert.Equal(t, "sb-my-sandbox", mock.lastCreateReq.GetSandboxRef().GetId()) } func TestFileUpload_ResolutionError(t *testing.T) { @@ -307,7 +307,7 @@ func TestFileDownload_ResolvesNameToID(t *testing.T) { localPath := filepath.Join(t.TempDir(), "downloaded.txt") _ = client.Download(context.Background(), "default", "my-sandbox", "/remote/file.txt", localPath) - assert.Equal(t, "sb-my-sandbox", mock.lastCreateReq.GetSandboxId()) + assert.Equal(t, "sb-my-sandbox", mock.lastCreateReq.GetSandboxRef().GetId()) } func TestFileDownload_ResolutionError(t *testing.T) { diff --git a/sdk/go/openshell/v1/internal/converter/exec.go b/sdk/go/openshell/v1/internal/converter/exec.go index 3c73f0c23..f427a59c4 100644 --- a/sdk/go/openshell/v1/internal/converter/exec.go +++ b/sdk/go/openshell/v1/internal/converter/exec.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" + dm "github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1" pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1" ) @@ -39,8 +40,10 @@ func ExecChunkFromEvent(event *pb.ExecSandboxEvent) (*types.ExecChunk, int, erro // ExecRequestToProto builds a proto ExecSandboxRequest for Run/Stream modes. func ExecRequestToProto(sandboxID string, command []string, opts *types.ExecOptions) *pb.ExecSandboxRequest { req := &pb.ExecSandboxRequest{ - SandboxId: sandboxID, - Command: CopyStringSlice(command), + SandboxRef: &dm.SandboxReference{ + Identifier: &dm.SandboxReference_Id{Id: sandboxID}, + }, + Command: CopyStringSlice(command), } if opts != nil { req.Workdir = opts.WorkDir diff --git a/sdk/go/openshell/v1/internal/converter/exec_test.go b/sdk/go/openshell/v1/internal/converter/exec_test.go index 078a72d4d..9dd7ccd22 100644 --- a/sdk/go/openshell/v1/internal/converter/exec_test.go +++ b/sdk/go/openshell/v1/internal/converter/exec_test.go @@ -117,7 +117,7 @@ func TestExecRequestToProto(t *testing.T) { }) require.NotNil(t, req) - assert.Equal(t, "sb-1", req.SandboxId) + assert.Equal(t, "sb-1", req.GetSandboxRef().GetId()) assert.Equal(t, []string{"ls", "-la"}, req.Command) assert.Equal(t, "/home/user", req.Workdir) assert.Equal(t, map[string]string{"FOO": "bar"}, req.Environment) @@ -128,7 +128,7 @@ func TestExecRequestToProto_NilOptions(t *testing.T) { req := ExecRequestToProto("sb-2", []string{"echo", "hi"}, nil) require.NotNil(t, req) - assert.Equal(t, "sb-2", req.SandboxId) + assert.Equal(t, "sb-2", req.GetSandboxRef().GetId()) assert.Equal(t, []string{"echo", "hi"}, req.Command) assert.Empty(t, req.Workdir) assert.Nil(t, req.Environment) @@ -141,7 +141,7 @@ func TestExecRequestToProto_Interactive(t *testing.T) { }) require.NotNil(t, req) - assert.Equal(t, "sb-3", req.SandboxId) + assert.Equal(t, "sb-3", req.GetSandboxRef().GetId()) assert.Equal(t, []string{"/bin/bash"}, req.Command) assert.Equal(t, "/root", req.Workdir) assert.Equal(t, map[string]string{"TERM": "xterm"}, req.Environment) diff --git a/sdk/go/openshell/v1/internal/converter/setting.go b/sdk/go/openshell/v1/internal/converter/setting.go index 495545938..3dc41fab2 100644 --- a/sdk/go/openshell/v1/internal/converter/setting.go +++ b/sdk/go/openshell/v1/internal/converter/setting.go @@ -7,6 +7,7 @@ import ( "fmt" v1 "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" + dm "github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1" pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1" sbv1 "github.com/NVIDIA/OpenShell/sdk/go/proto/sandboxv1" ) @@ -180,7 +181,6 @@ func ConfigUpdateToProto(cu *v1.ConfigUpdate) (*pb.UpdateConfigRequest, error) { return nil, nil } req := &pb.UpdateConfigRequest{ - Name: cu.Name, SettingKey: cu.SettingKey, SettingValue: SettingValueToProto(cu.SettingValue), DeleteSetting: cu.DeleteSetting, @@ -188,6 +188,11 @@ func ConfigUpdateToProto(cu *v1.ConfigUpdate) (*pb.UpdateConfigRequest, error) { ExpectedResourceVersion: cu.ExpectedResourceVersion, Annotations: CopyStringMap(cu.Annotations), } + if !cu.Global { + req.SandboxRef = &dm.SandboxReference{ + Identifier: &dm.SandboxReference_Name{Name: cu.Name}, + } + } // Convert typed SDK SandboxPolicy to proto SandboxPolicy. policy, err := SandboxPolicyToProtoChecked(cu.Policy) diff --git a/sdk/go/openshell/v1/internal/converter/setting_test.go b/sdk/go/openshell/v1/internal/converter/setting_test.go index f54690242..8d2f8ffa7 100644 --- a/sdk/go/openshell/v1/internal/converter/setting_test.go +++ b/sdk/go/openshell/v1/internal/converter/setting_test.go @@ -429,7 +429,7 @@ func TestConfigUpdateToProto(t *testing.T) { require.NoError(t, err) require.NotNil(t, req) - assert.Equal(t, "my-sandbox", req.Name) + assert.Equal(t, "my-sandbox", req.GetSandboxRef().GetName()) assert.Equal(t, "timeout", req.SettingKey) require.NotNil(t, req.SettingValue) assert.Equal(t, int64(60), req.SettingValue.GetIntValue()) @@ -495,7 +495,7 @@ func TestConfigUpdateToProto_GlobalScope(t *testing.T) { require.NotNil(t, req) assert.True(t, req.Global) - assert.Empty(t, req.Name) + assert.Nil(t, req.GetSandboxRef()) } func TestConfigUpdateToProto_NilSettingValue(t *testing.T) { @@ -779,7 +779,7 @@ func TestConfigUpdateToProto_WithMergeOperations(t *testing.T) { require.NoError(t, err) require.NotNil(t, req) - assert.Equal(t, "my-sandbox", req.GetName()) + assert.Equal(t, "my-sandbox", req.GetSandboxRef().GetName()) require.Len(t, req.GetMergeOperations(), 3) // First: RemoveRule diff --git a/sdk/go/openshell/v1/policy.go b/sdk/go/openshell/v1/policy.go index cbe2e8621..1cb4433d8 100644 --- a/sdk/go/openshell/v1/policy.go +++ b/sdk/go/openshell/v1/policy.go @@ -111,7 +111,7 @@ type PolicyInterface interface { ClearDraftChunks(ctx context.Context, workspace, sandboxName string) (*ClearResult, error) GetDraftHistory(ctx context.Context, workspace, sandboxName string) ([]DraftHistoryEntry, error) GetStatus(ctx context.Context, workspace, sandboxName string, opts ...GetStatusOption) (*PolicyStatusResult, error) - List(ctx context.Context, workspace string, opts ...ListPolicyOption) ([]SandboxPolicyRevision, error) + List(ctx context.Context, workspace, sandboxName string, opts ...ListPolicyOption) ([]SandboxPolicyRevision, error) EditDraftChunk(ctx context.Context, workspace, sandboxName, chunkID string, proposedRule *NetworkPolicyRule) error UndoDraftChunk(ctx context.Context, workspace, sandboxName, chunkID string) (*UndoResult, error) } diff --git a/sdk/go/openshell/v1/policy_client.go b/sdk/go/openshell/v1/policy_client.go index 12801af02..db9c6fa2d 100644 --- a/sdk/go/openshell/v1/policy_client.go +++ b/sdk/go/openshell/v1/policy_client.go @@ -23,9 +23,8 @@ func newPolicyClient(conn grpc.ClientConnInterface) *policyClient { func (p *policyClient) GetDraft(ctx context.Context, workspace, sandboxName string, opts ...GetDraftOption) (*DraftPolicy, error) { cfg := types.ApplyGetDraftOptions(opts) resp, err := p.client.GetDraftPolicy(ctx, &pb.GetDraftPolicyRequest{ - Name: sandboxName, - StatusFilter: cfg.StatusFilter(), - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, sandboxName), + StatusFilter: cfg.StatusFilter(), }) if err != nil { return nil, converter.FromGRPCError(err) @@ -35,10 +34,9 @@ func (p *policyClient) GetDraft(ctx context.Context, workspace, sandboxName stri func (p *policyClient) ApproveDraftChunk(ctx context.Context, workspace, sandboxName, chunkID, reviewToken string) (*ApproveResult, error) { resp, err := p.client.ApproveDraftChunk(ctx, &pb.ApproveDraftChunkRequest{ - Name: sandboxName, - ChunkId: chunkID, - WorkspaceScope: namedWorkspaceScope(workspace), - ReviewToken: reviewToken, + SandboxRef: sandboxReferenceByName(workspace, sandboxName), + ChunkId: chunkID, + ReviewToken: reviewToken, }) if err != nil { return nil, converter.FromGRPCError(err) @@ -48,10 +46,9 @@ func (p *policyClient) ApproveDraftChunk(ctx context.Context, workspace, sandbox func (p *policyClient) RejectDraftChunk(ctx context.Context, workspace, sandboxName, chunkID, reason string) error { _, err := p.client.RejectDraftChunk(ctx, &pb.RejectDraftChunkRequest{ - Name: sandboxName, - ChunkId: chunkID, - Reason: reason, - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, sandboxName), + ChunkId: chunkID, + Reason: reason, }) if err != nil { return converter.FromGRPCError(err) @@ -69,10 +66,9 @@ func (p *policyClient) ApproveAllDraftChunks(ctx context.Context, workspace, san }) } resp, err := p.client.ApproveAllDraftChunks(ctx, &pb.ApproveAllDraftChunksRequest{ - Name: sandboxName, IncludeSecurityFlagged: cfg.IncludeSecurityFlagged(), - WorkspaceScope: namedWorkspaceScope(workspace), Approvals: approvals, + SandboxRef: sandboxReferenceByName(workspace, sandboxName), }) if err != nil { return nil, converter.FromGRPCError(err) @@ -82,8 +78,7 @@ func (p *policyClient) ApproveAllDraftChunks(ctx context.Context, workspace, san func (p *policyClient) ClearDraftChunks(ctx context.Context, workspace, sandboxName string) (*ClearResult, error) { resp, err := p.client.ClearDraftChunks(ctx, &pb.ClearDraftChunksRequest{ - Name: sandboxName, - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, sandboxName), }) if err != nil { return nil, converter.FromGRPCError(err) @@ -93,8 +88,7 @@ func (p *policyClient) ClearDraftChunks(ctx context.Context, workspace, sandboxN func (p *policyClient) GetDraftHistory(ctx context.Context, workspace, sandboxName string) ([]DraftHistoryEntry, error) { resp, err := p.client.GetDraftHistory(ctx, &pb.GetDraftHistoryRequest{ - Name: sandboxName, - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, sandboxName), }) if err != nil { return nil, converter.FromGRPCError(err) @@ -115,12 +109,11 @@ func (p *policyClient) GetDraftHistory(ctx context.Context, workspace, sandboxNa func (p *policyClient) GetStatus(ctx context.Context, workspace, sandboxName string, opts ...GetStatusOption) (*PolicyStatusResult, error) { cfg := types.ApplyGetStatusOptions(opts) req := &pb.GetSandboxPolicyStatusRequest{ - Name: sandboxName, Version: cfg.Version(), Global: cfg.Global(), } if !cfg.Global() { - req.WorkspaceScope = namedWorkspaceScope(workspace) + req.SandboxRef = sandboxReferenceByName(workspace, sandboxName) } resp, err := p.client.GetSandboxPolicyStatus(ctx, req) if err != nil { @@ -129,7 +122,7 @@ func (p *policyClient) GetStatus(ctx context.Context, workspace, sandboxName str return converter.PolicyStatusResultFromProto(resp), nil } -func (p *policyClient) List(ctx context.Context, workspace string, opts ...ListPolicyOption) ([]SandboxPolicyRevision, error) { +func (p *policyClient) List(ctx context.Context, workspace, sandboxName string, opts ...ListPolicyOption) ([]SandboxPolicyRevision, error) { cfg := types.ApplyListPolicyOptions(opts) req := &pb.ListSandboxPoliciesRequest{ Limit: cfg.Limit(), @@ -137,7 +130,7 @@ func (p *policyClient) List(ctx context.Context, workspace string, opts ...ListP Global: cfg.Global(), } if !cfg.Global() { - req.WorkspaceScope = namedWorkspaceScope(workspace) + req.SandboxRef = sandboxReferenceByName(workspace, sandboxName) } resp, err := p.client.ListSandboxPolicies(ctx, req) if err != nil { @@ -158,10 +151,9 @@ func (p *policyClient) List(ctx context.Context, workspace string, opts ...ListP func (p *policyClient) EditDraftChunk(ctx context.Context, workspace, sandboxName, chunkID string, proposedRule *NetworkPolicyRule) error { _, err := p.client.EditDraftChunk(ctx, &pb.EditDraftChunkRequest{ - Name: sandboxName, - ChunkId: chunkID, - ProposedRule: converter.NetworkPolicyRuleToProto(proposedRule), - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, sandboxName), + ChunkId: chunkID, + ProposedRule: converter.NetworkPolicyRuleToProto(proposedRule), }) if err != nil { return converter.FromGRPCError(err) @@ -171,9 +163,8 @@ func (p *policyClient) EditDraftChunk(ctx context.Context, workspace, sandboxNam func (p *policyClient) UndoDraftChunk(ctx context.Context, workspace, sandboxName, chunkID string) (*UndoResult, error) { resp, err := p.client.UndoDraftChunk(ctx, &pb.UndoDraftChunkRequest{ - Name: sandboxName, - ChunkId: chunkID, - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, sandboxName), + ChunkId: chunkID, }) if err != nil { return nil, converter.FromGRPCError(err) diff --git a/sdk/go/openshell/v1/policy_client_test.go b/sdk/go/openshell/v1/policy_client_test.go index a0e16f5a4..adac5cf00 100644 --- a/sdk/go/openshell/v1/policy_client_test.go +++ b/sdk/go/openshell/v1/policy_client_test.go @@ -235,7 +235,7 @@ func TestPolicyGetDraft(t *testing.T) { // Verify request was forwarded. mock.mu.Lock() - assert.Equal(t, "my-sandbox", mock.lastGetDraftReq.GetName()) + assert.Equal(t, "my-sandbox", mock.lastGetDraftReq.GetSandboxRef().GetName()) assert.Empty(t, mock.lastGetDraftReq.GetStatusFilter()) mock.mu.Unlock() @@ -321,7 +321,7 @@ func TestPolicyApproveDraftChunk(t *testing.T) { // Verify request was forwarded. mock.mu.Lock() - assert.Equal(t, "my-sandbox", mock.lastApproveReq.GetName()) + assert.Equal(t, "my-sandbox", mock.lastApproveReq.GetSandboxRef().GetName()) assert.Equal(t, "chunk-1", mock.lastApproveReq.GetChunkId()) assert.Equal(t, "token-1", mock.lastApproveReq.GetReviewToken()) mock.mu.Unlock() @@ -358,7 +358,7 @@ func TestPolicyRejectDraftChunk(t *testing.T) { // Verify request was forwarded. mock.mu.Lock() - assert.Equal(t, "my-sandbox", mock.lastRejectReq.GetName()) + assert.Equal(t, "my-sandbox", mock.lastRejectReq.GetSandboxRef().GetName()) assert.Equal(t, "chunk-2", mock.lastRejectReq.GetChunkId()) assert.Equal(t, "too broad", mock.lastRejectReq.GetReason()) mock.mu.Unlock() @@ -400,7 +400,7 @@ func TestPolicyApproveAllDraftChunks(t *testing.T) { // Verify default: security-flagged NOT included. mock.mu.Lock() - assert.Equal(t, "my-sandbox", mock.lastApproveAllReq.GetName()) + assert.Equal(t, "my-sandbox", mock.lastApproveAllReq.GetSandboxRef().GetName()) assert.False(t, mock.lastApproveAllReq.GetIncludeSecurityFlagged()) mock.mu.Unlock() @@ -466,7 +466,7 @@ func TestPolicyClearDraftChunks(t *testing.T) { // Verify request was forwarded. mock.mu.Lock() - assert.Equal(t, "my-sandbox", mock.lastClearReq.GetName()) + assert.Equal(t, "my-sandbox", mock.lastClearReq.GetSandboxRef().GetName()) mock.mu.Unlock() assert.Equal(t, uint32(4), result.ChunksCleared) @@ -517,7 +517,7 @@ func TestPolicyGetDraftHistory(t *testing.T) { // Verify request was forwarded. mock.mu.Lock() - assert.Equal(t, "my-sandbox", mock.lastHistoryReq.GetName()) + assert.Equal(t, "my-sandbox", mock.lastHistoryReq.GetSandboxRef().GetName()) mock.mu.Unlock() assert.Equal(t, "approved", entries[0].EventType) @@ -583,7 +583,7 @@ func TestPolicyGetStatus(t *testing.T) { // Verify request was forwarded (no version = latest). mock.mu.Lock() - assert.Equal(t, "my-sandbox", mock.lastStatusReq.GetName()) + assert.Equal(t, "my-sandbox", mock.lastStatusReq.GetSandboxRef().GetName()) assert.Equal(t, uint32(0), mock.lastStatusReq.GetVersion()) mock.mu.Unlock() @@ -649,8 +649,7 @@ func TestPolicyGetStatus_WithGlobal(t *testing.T) { // Verify global flag was forwarded in the proto request. mock.mu.Lock() assert.True(t, mock.lastStatusReq.GetGlobal()) - assert.Empty(t, mock.lastStatusReq.GetName()) - assert.Nil(t, mock.lastStatusReq.GetWorkspaceScope()) + assert.Nil(t, mock.lastStatusReq.GetSandboxRef()) mock.mu.Unlock() } @@ -675,8 +674,7 @@ func TestPolicyGetStatus_WithGlobalIgnoresNonEmptyName(t *testing.T) { mock.mu.Lock() assert.True(t, mock.lastStatusReq.GetGlobal()) - assert.Equal(t, "some-sandbox", mock.lastStatusReq.GetName()) - assert.Nil(t, mock.lastStatusReq.GetWorkspaceScope()) + assert.Nil(t, mock.lastStatusReq.GetSandboxRef()) mock.mu.Unlock() } @@ -731,8 +729,8 @@ func TestPolicyGetStatus_WithoutGlobal_PreservesExistingBehavior(t *testing.T) { // Verify global flag is false by default. mock.mu.Lock() assert.False(t, mock.lastStatusReq.GetGlobal()) - assert.Equal(t, "default", mock.lastStatusReq.GetWorkspaceScope().GetWorkspace()) - assert.Equal(t, "my-sandbox", mock.lastStatusReq.GetName()) + assert.Equal(t, "default", mock.lastStatusReq.GetSandboxRef().GetWorkspaceScope().GetWorkspace()) + assert.Equal(t, "my-sandbox", mock.lastStatusReq.GetSandboxRef().GetName()) mock.mu.Unlock() } @@ -773,14 +771,15 @@ func TestPolicyList(t *testing.T) { client, cleanup := setupPolicyTest(t, mock) defer cleanup() - revisions, err := client.List(context.Background(), "default") + revisions, err := client.List(context.Background(), "default", "my-sandbox") require.NoError(t, err) require.Len(t, revisions, 2) // Verify request was forwarded (no pagination options). mock.mu.Lock() - assert.Equal(t, "default", mock.lastListReq.GetWorkspaceScope().GetWorkspace()) + assert.Equal(t, "my-sandbox", mock.lastListReq.GetSandboxRef().GetName()) + assert.Equal(t, "default", mock.lastListReq.GetSandboxRef().GetWorkspaceScope().GetWorkspace()) assert.Equal(t, uint32(0), mock.lastListReq.GetLimit()) assert.Equal(t, uint32(0), mock.lastListReq.GetOffset()) mock.mu.Unlock() @@ -805,7 +804,7 @@ func TestPolicyList_WithPagination(t *testing.T) { client, cleanup := setupPolicyTest(t, mock) defer cleanup() - revisions, err := client.List(context.Background(), "default", + revisions, err := client.List(context.Background(), "default", "my-sandbox", types.WithLimit(10), types.WithOffset(20), ) @@ -827,7 +826,7 @@ func TestPolicyList_Empty(t *testing.T) { client, cleanup := setupPolicyTest(t, mock) defer cleanup() - revisions, err := client.List(context.Background(), "default") + revisions, err := client.List(context.Background(), "default", "my-sandbox") require.NoError(t, err) assert.Nil(t, revisions) @@ -845,7 +844,7 @@ func TestPolicyList_WithGlobal(t *testing.T) { defer cleanup() // List with global flag and empty workspace. - revisions, err := client.List(context.Background(), "", types.WithListGlobal(true)) + revisions, err := client.List(context.Background(), "", "", types.WithListGlobal(true)) require.NoError(t, err) require.Len(t, revisions, 1) @@ -855,7 +854,7 @@ func TestPolicyList_WithGlobal(t *testing.T) { // Verify global flag was forwarded in the proto request. mock.mu.Lock() assert.True(t, mock.lastListReq.GetGlobal()) - assert.Nil(t, mock.lastListReq.GetWorkspaceScope()) + assert.Nil(t, mock.lastListReq.GetSandboxRef()) mock.mu.Unlock() } @@ -870,14 +869,14 @@ func TestPolicyList_WithGlobalIgnoresWorkspace(t *testing.T) { client, cleanup := setupPolicyTest(t, mock) defer cleanup() - revisions, err := client.List(context.Background(), "some-workspace", types.WithListGlobal(true)) + revisions, err := client.List(context.Background(), "some-workspace", "", types.WithListGlobal(true)) require.NoError(t, err) require.Len(t, revisions, 1) mock.mu.Lock() assert.True(t, mock.lastListReq.GetGlobal()) - assert.Nil(t, mock.lastListReq.GetWorkspaceScope()) + assert.Nil(t, mock.lastListReq.GetSandboxRef()) mock.mu.Unlock() } @@ -893,7 +892,7 @@ func TestPolicyList_WithGlobalAndPagination(t *testing.T) { defer cleanup() // Global flag composes with pagination options. - revisions, err := client.List(context.Background(), "", + revisions, err := client.List(context.Background(), "", "", types.WithListGlobal(true), types.WithLimit(10), types.WithOffset(20), @@ -920,7 +919,7 @@ func TestPolicyList_WithoutGlobal_PreservesExistingBehavior(t *testing.T) { client, cleanup := setupPolicyTest(t, mock) defer cleanup() - revisions, err := client.List(context.Background(), "default") + revisions, err := client.List(context.Background(), "default", "my-sandbox") require.NoError(t, err) require.Len(t, revisions, 1) @@ -928,7 +927,8 @@ func TestPolicyList_WithoutGlobal_PreservesExistingBehavior(t *testing.T) { // Verify global flag is false by default. mock.mu.Lock() assert.False(t, mock.lastListReq.GetGlobal()) - assert.Equal(t, "default", mock.lastListReq.GetWorkspaceScope().GetWorkspace()) + assert.Equal(t, "my-sandbox", mock.lastListReq.GetSandboxRef().GetName()) + assert.Equal(t, "default", mock.lastListReq.GetSandboxRef().GetWorkspaceScope().GetWorkspace()) mock.mu.Unlock() } @@ -939,7 +939,7 @@ func TestPolicyList_Error(t *testing.T) { client, cleanup := setupPolicyTest(t, mock) defer cleanup() - revisions, err := client.List(context.Background(), "default") + revisions, err := client.List(context.Background(), "default", "my-sandbox") assert.Nil(t, revisions) require.Error(t, err) @@ -966,7 +966,7 @@ func TestPolicyEditDraftChunk(t *testing.T) { // Verify request was forwarded. mock.mu.Lock() - assert.Equal(t, "my-sandbox", mock.lastEditReq.GetName()) + assert.Equal(t, "my-sandbox", mock.lastEditReq.GetSandboxRef().GetName()) assert.Equal(t, "chunk-1", mock.lastEditReq.GetChunkId()) require.NotNil(t, mock.lastEditReq.GetProposedRule()) assert.Equal(t, "allow-https", mock.lastEditReq.GetProposedRule().GetName()) @@ -1005,7 +1005,7 @@ func TestPolicyUndoDraftChunk(t *testing.T) { // Verify request was forwarded. mock.mu.Lock() - assert.Equal(t, "my-sandbox", mock.lastUndoReq.GetName()) + assert.Equal(t, "my-sandbox", mock.lastUndoReq.GetSandboxRef().GetName()) assert.Equal(t, "chunk-3", mock.lastUndoReq.GetChunkId()) mock.mu.Unlock() diff --git a/sdk/go/openshell/v1/sandbox_client.go b/sdk/go/openshell/v1/sandbox_client.go index 4028634e2..3e047774d 100644 --- a/sdk/go/openshell/v1/sandbox_client.go +++ b/sdk/go/openshell/v1/sandbox_client.go @@ -89,8 +89,7 @@ func validateTemplateCreateSpec(spec *SandboxSpec) error { func (s *sandboxClient) Get(ctx context.Context, workspace, name string) (*Sandbox, error) { resp, err := s.client.GetSandbox(ctx, &pb.GetSandboxRequest{ - Name: name, - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, name), }) if err != nil { return nil, converter.FromGRPCError(err) @@ -136,8 +135,7 @@ func (s *sandboxClient) list(ctx context.Context, req *pb.ListSandboxesRequest, func (s *sandboxClient) Delete(ctx context.Context, workspace, name string) error { _, err := s.client.DeleteSandbox(ctx, &pb.DeleteSandboxRequest{ - Name: name, - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, name), }) if err != nil { return converter.FromGRPCError(err) @@ -147,8 +145,7 @@ func (s *sandboxClient) Delete(ctx context.Context, workspace, name string) erro func (s *sandboxClient) Stop(ctx context.Context, workspace, name string) (*Sandbox, error) { resp, err := s.client.StopSandbox(ctx, &pb.StopSandboxRequest{ - Name: name, - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, name), }) if err != nil { return nil, converter.FromGRPCError(err) @@ -158,8 +155,7 @@ func (s *sandboxClient) Stop(ctx context.Context, workspace, name string) (*Sand func (s *sandboxClient) Start(ctx context.Context, workspace, name string) (*Sandbox, error) { resp, err := s.client.StartSandbox(ctx, &pb.StartSandboxRequest{ - Name: name, - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, name), }) if err != nil { return nil, converter.FromGRPCError(err) @@ -169,10 +165,9 @@ func (s *sandboxClient) Start(ctx context.Context, workspace, name string) (*San func (s *sandboxClient) AttachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*AttachProviderResult, error) { resp, err := s.client.AttachSandboxProvider(ctx, &pb.AttachSandboxProviderRequest{ - SandboxName: sandboxName, ProviderName: providerName, ExpectedResourceVersion: expectedResourceVersion, - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, sandboxName), }) if err != nil { return nil, converter.FromGRPCError(err) @@ -185,10 +180,9 @@ func (s *sandboxClient) AttachProvider(ctx context.Context, workspace, sandboxNa func (s *sandboxClient) DetachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*DetachProviderResult, error) { resp, err := s.client.DetachSandboxProvider(ctx, &pb.DetachSandboxProviderRequest{ - SandboxName: sandboxName, ProviderName: providerName, ExpectedResourceVersion: expectedResourceVersion, - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, sandboxName), }) if err != nil { return nil, converter.FromGRPCError(err) @@ -201,8 +195,7 @@ func (s *sandboxClient) DetachProvider(ctx context.Context, workspace, sandboxNa func (s *sandboxClient) ListProviders(ctx context.Context, workspace, sandboxName string) ([]*Provider, error) { resp, err := s.client.ListSandboxProviders(ctx, &pb.ListSandboxProvidersRequest{ - SandboxName: sandboxName, - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, sandboxName), }) if err != nil { return nil, converter.FromGRPCError(err) @@ -295,7 +288,7 @@ func (s *sandboxClient) Watch(ctx context.Context, workspace, name string, opts streamCtx, streamCancel := context.WithCancel(ctx) stream, err := s.client.WatchSandbox(streamCtx, &pb.WatchSandboxRequest{ - Id: sb.ID, + SandboxRef: sandboxReferenceByID(sb.ID), FollowStatus: true, StopOnTerminal: watchOpts.StopOnTerminal, }) @@ -368,11 +361,10 @@ func (s *sandboxClient) GetLogs(ctx context.Context, workspace, sandboxName stri cfg := types.ApplyLogOptions(opts) req := &pb.GetSandboxLogsRequest{ - SandboxId: sb.ID, - Lines: cfg.Lines(), - Sources: cfg.Sources(), - MinLevel: cfg.MinLevel(), - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByID(sb.ID), + Lines: cfg.Lines(), + Sources: cfg.Sources(), + MinLevel: cfg.MinLevel(), } if !cfg.Since().IsZero() { req.SinceMs = converter.MillisFromTime(cfg.Since()) diff --git a/sdk/go/openshell/v1/sandbox_client_test.go b/sdk/go/openshell/v1/sandbox_client_test.go index 7926b28a1..5b47ad841 100644 --- a/sdk/go/openshell/v1/sandbox_client_test.go +++ b/sdk/go/openshell/v1/sandbox_client_test.go @@ -54,6 +54,14 @@ func newMockSandboxServer() *mockSandboxServer { } } +func sandboxRefName(ref *dm.SandboxReference) string { + return ref.GetName() +} + +func sandboxRefID(ref *dm.SandboxReference) string { + return ref.GetId() +} + func (s *mockSandboxServer) CreateSandbox(_ context.Context, req *pb.CreateSandboxRequest) (*pb.SandboxResponse, error) { s.mu.Lock() defer s.mu.Unlock() @@ -82,9 +90,10 @@ func (s *mockSandboxServer) GetSandbox(_ context.Context, req *pb.GetSandboxRequ if s.getErr != nil { return nil, s.getErr } - sb, ok := s.sandboxes[req.GetName()] + name := sandboxRefName(req.GetSandboxRef()) + sb, ok := s.sandboxes[name] if !ok { - return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName()) + return nil, status.Errorf(codes.NotFound, "sandbox %q not found", name) } cloned := proto.Clone(sb).(*pb.Sandbox) return &pb.SandboxResponse{Sandbox: cloned}, nil @@ -117,20 +126,22 @@ func (s *mockSandboxServer) DeleteSandbox(_ context.Context, req *pb.DeleteSandb if s.deleteErr != nil { return nil, s.deleteErr } - _, ok := s.sandboxes[req.GetName()] + name := sandboxRefName(req.GetSandboxRef()) + _, ok := s.sandboxes[name] if !ok { - return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName()) + return nil, status.Errorf(codes.NotFound, "sandbox %q not found", name) } - delete(s.sandboxes, req.GetName()) + delete(s.sandboxes, name) return &pb.DeleteSandboxResponse{Deleted: true}, nil } func (s *mockSandboxServer) StopSandbox(_ context.Context, req *pb.StopSandboxRequest) (*pb.SandboxResponse, error) { s.mu.Lock() defer s.mu.Unlock() - sb, ok := s.sandboxes[req.GetName()] + name := sandboxRefName(req.GetSandboxRef()) + sb, ok := s.sandboxes[name] if !ok { - return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName()) + return nil, status.Errorf(codes.NotFound, "sandbox %q not found", name) } sb.Status.Phase = pb.SandboxPhase_SANDBOX_PHASE_STOPPED return &pb.SandboxResponse{Sandbox: proto.Clone(sb).(*pb.Sandbox)}, nil @@ -139,9 +150,10 @@ func (s *mockSandboxServer) StopSandbox(_ context.Context, req *pb.StopSandboxRe func (s *mockSandboxServer) StartSandbox(_ context.Context, req *pb.StartSandboxRequest) (*pb.SandboxResponse, error) { s.mu.Lock() defer s.mu.Unlock() - sb, ok := s.sandboxes[req.GetName()] + name := sandboxRefName(req.GetSandboxRef()) + sb, ok := s.sandboxes[name] if !ok { - return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName()) + return nil, status.Errorf(codes.NotFound, "sandbox %q not found", name) } sb.Status.Phase = pb.SandboxPhase_SANDBOX_PHASE_STARTING return &pb.SandboxResponse{Sandbox: proto.Clone(sb).(*pb.Sandbox)}, nil @@ -153,9 +165,10 @@ func (s *mockSandboxServer) AttachSandboxProvider(_ context.Context, req *pb.Att if s.attachErr != nil { return nil, s.attachErr } - sb, ok := s.sandboxes[req.GetSandboxName()] + name := sandboxRefName(req.GetSandboxRef()) + sb, ok := s.sandboxes[name] if !ok { - return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetSandboxName()) + return nil, status.Errorf(codes.NotFound, "sandbox %q not found", name) } sb.Spec.Providers = append(sb.Spec.Providers, req.GetProviderName()) return &pb.AttachSandboxProviderResponse{Sandbox: sb, Attached: true}, nil @@ -167,9 +180,10 @@ func (s *mockSandboxServer) DetachSandboxProvider(_ context.Context, req *pb.Det if s.detachErr != nil { return nil, s.detachErr } - sb, ok := s.sandboxes[req.GetSandboxName()] + name := sandboxRefName(req.GetSandboxRef()) + sb, ok := s.sandboxes[name] if !ok { - return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetSandboxName()) + return nil, status.Errorf(codes.NotFound, "sandbox %q not found", name) } return &pb.DetachSandboxProviderResponse{Sandbox: sb, Detached: true}, nil } @@ -178,7 +192,7 @@ func (s *mockSandboxServer) ListSandboxProviders(_ context.Context, req *pb.List if s.listProvErr != nil { return nil, s.listProvErr } - provs := s.providers[req.GetSandboxName()] + provs := s.providers[sandboxRefName(req.GetSandboxRef())] return &pb.ListSandboxProvidersResponse{Providers: provs}, nil } @@ -945,12 +959,12 @@ func TestSandboxWatch_ResolvesNameToID(t *testing.T) { require.NoError(t, err) defer w.Stop() - // Verify the WatchSandboxRequest.Id contains the resolved ID, not the name + // Verify the WatchSandboxRequest reference contains the resolved ID, not the name. mock.mu.Lock() req := mock.watchRequest mock.mu.Unlock() require.NotNil(t, req) - assert.Equal(t, "resolved-id-123", req.GetId(), "Watch should send resolved sandbox ID, not the name") + assert.Equal(t, "resolved-id-123", req.GetSandboxRef().GetId(), "Watch should send resolved sandbox ID, not the name") } func TestSandboxWatch_ResolutionError(t *testing.T) { @@ -1161,7 +1175,7 @@ func TestSandboxGetLogs(t *testing.T) { // Verify name→id resolution: the proto request should contain the sandbox ID mock.mu.Lock() - assert.Equal(t, "sb-id-123", mock.getLogsRequest.GetSandboxId()) + assert.Equal(t, "sb-id-123", mock.getLogsRequest.GetSandboxRef().GetId()) mock.mu.Unlock() } @@ -1195,7 +1209,7 @@ func TestSandboxGetLogs_WithOptions(t *testing.T) { mock.mu.Lock() req := mock.getLogsRequest mock.mu.Unlock() - assert.Equal(t, "sb-id-opts", req.GetSandboxId()) + assert.Equal(t, "sb-id-opts", req.GetSandboxRef().GetId()) assert.Equal(t, uint32(50), req.GetLines()) assert.Equal(t, since.UnixMilli(), req.GetSinceMs()) assert.Equal(t, []string{"gateway", "sandbox"}, req.GetSources()) diff --git a/sdk/go/openshell/v1/service_client.go b/sdk/go/openshell/v1/service_client.go index 86013f8b3..3e3a5726c 100644 --- a/sdk/go/openshell/v1/service_client.go +++ b/sdk/go/openshell/v1/service_client.go @@ -21,11 +21,10 @@ func newServiceClient(conn grpc.ClientConnInterface) *serviceClient { func (s *serviceClient) Expose(ctx context.Context, workspace, sandboxName, serviceName string, targetPort uint32, domain bool) (*ServiceEndpoint, error) { resp, err := s.client.ExposeService(ctx, &pb.ExposeServiceRequest{ - Sandbox: sandboxName, - Service: serviceName, - TargetPort: targetPort, - Domain: domain, - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, sandboxName), + Service: serviceName, + TargetPort: targetPort, + Domain: domain, }) if err != nil { return nil, converter.FromGRPCError(err) @@ -35,9 +34,8 @@ func (s *serviceClient) Expose(ctx context.Context, workspace, sandboxName, serv func (s *serviceClient) Get(ctx context.Context, workspace, sandboxName, serviceName string) (*ServiceEndpoint, error) { resp, err := s.client.GetService(ctx, &pb.GetServiceRequest{ - Sandbox: sandboxName, - Service: serviceName, - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, sandboxName), + Service: serviceName, }) if err != nil { return nil, converter.FromGRPCError(err) @@ -46,9 +44,10 @@ func (s *serviceClient) Get(ctx context.Context, workspace, sandboxName, service } func (s *serviceClient) List(ctx context.Context, workspace, sandboxName string, opts ...ListOptions) ([]*ServiceEndpoint, error) { - req := &pb.ListServicesRequest{ - Sandbox: sandboxName, - WorkspaceScope: namedWorkspaceScope(workspace), + req := &pb.ListServicesRequest{WorkspaceScope: namedWorkspaceScope(workspace)} + if sandboxName != "" { + req.SandboxRef = sandboxReferenceByName(workspace, sandboxName) + req.WorkspaceScope = nil } return s.list(ctx, req, opts...) } @@ -83,9 +82,8 @@ func (s *serviceClient) list(ctx context.Context, req *pb.ListServicesRequest, o func (s *serviceClient) Delete(ctx context.Context, workspace, sandboxName, serviceName string) error { _, err := s.client.DeleteService(ctx, &pb.DeleteServiceRequest{ - Sandbox: sandboxName, - Service: serviceName, - WorkspaceScope: namedWorkspaceScope(workspace), + SandboxRef: sandboxReferenceByName(workspace, sandboxName), + Service: serviceName, }) if err != nil { return converter.FromGRPCError(err) diff --git a/sdk/go/openshell/v1/service_client_test.go b/sdk/go/openshell/v1/service_client_test.go index bf0b4eccb..214ed1fd6 100644 --- a/sdk/go/openshell/v1/service_client_test.go +++ b/sdk/go/openshell/v1/service_client_test.go @@ -55,7 +55,7 @@ func (s *mockServiceServer) ExposeService(_ context.Context, req *pb.ExposeServi Metadata: &dm.ObjectMeta{ Id: "ep-" + req.GetService(), }, - SandboxName: req.GetSandbox(), + SandboxName: sandboxRefName(req.GetSandboxRef()), ServiceName: req.GetService(), TargetPort: req.GetTargetPort(), Domain: req.GetDomain(), @@ -65,7 +65,7 @@ func (s *mockServiceServer) ExposeService(_ context.Context, req *pb.ExposeServi resp.Url = "https://" + req.GetService() + ".example.com" } - s.endpoints[serviceKey(req.GetSandbox(), req.GetService())] = resp + s.endpoints[serviceKey(sandboxRefName(req.GetSandboxRef()), req.GetService())] = resp return resp, nil } @@ -76,9 +76,10 @@ func (s *mockServiceServer) GetService(_ context.Context, req *pb.GetServiceRequ return nil, s.getErr } - ep, ok := s.endpoints[serviceKey(req.GetSandbox(), req.GetService())] + sandboxName := sandboxRefName(req.GetSandboxRef()) + ep, ok := s.endpoints[serviceKey(sandboxName, req.GetService())] if !ok { - return nil, status.Errorf(codes.NotFound, "service %q not found in sandbox %q", req.GetService(), req.GetSandbox()) + return nil, status.Errorf(codes.NotFound, "service %q not found in sandbox %q", req.GetService(), sandboxName) } return ep, nil } @@ -93,8 +94,9 @@ func (s *mockServiceServer) ListServices(_ context.Context, req *pb.ListServices var services []*pb.ServiceEndpointResponse for key, ep := range s.endpoints { - prefix := req.GetSandbox() + "/" - if req.GetSandbox() == "" || (len(key) >= len(prefix) && key[:len(prefix)] == prefix) { + sandboxName := sandboxRefName(req.GetSandboxRef()) + prefix := sandboxName + "/" + if sandboxName == "" || (len(key) >= len(prefix) && key[:len(prefix)] == prefix) { services = append(services, ep) } } @@ -108,10 +110,11 @@ func (s *mockServiceServer) DeleteService(_ context.Context, req *pb.DeleteServi return nil, s.deleteErr } - key := serviceKey(req.GetSandbox(), req.GetService()) + sandboxName := sandboxRefName(req.GetSandboxRef()) + key := serviceKey(sandboxName, req.GetService()) _, ok := s.endpoints[key] if !ok { - return nil, status.Errorf(codes.NotFound, "service %q not found in sandbox %q", req.GetService(), req.GetSandbox()) + return nil, status.Errorf(codes.NotFound, "service %q not found in sandbox %q", req.GetService(), sandboxName) } delete(s.endpoints, key) return &pb.DeleteServiceResponse{Deleted: true}, nil @@ -278,7 +281,7 @@ func TestServiceListAll_SelectsAllWorkspaces(t *testing.T) { require.NoError(t, err) assert.Empty(t, endpoints) require.NotNil(t, mock.lastList) - assert.Empty(t, mock.lastList.GetSandbox()) + assert.Nil(t, mock.lastList.GetSandboxRef()) assert.NotNil(t, mock.lastList.GetWorkspaceScope().GetAllWorkspaces()) } diff --git a/sdk/go/openshell/v1/ssh_client.go b/sdk/go/openshell/v1/ssh_client.go index 6e7ee1a46..01a0fda1c 100644 --- a/sdk/go/openshell/v1/ssh_client.go +++ b/sdk/go/openshell/v1/ssh_client.go @@ -32,7 +32,7 @@ func newSSHClient(conn grpc.ClientConnInterface, sandboxes SandboxInterface) *ss func (s *sshClient) CreateSession(ctx context.Context, _, sandboxID string) (*SSHSession, error) { resp, err := s.client.CreateSshSession(ctx, &pb.CreateSshSessionRequest{ - SandboxId: sandboxID, + SandboxRef: sandboxReferenceByID(sandboxID), }) if err != nil { return nil, converter.FromGRPCError(err) @@ -94,7 +94,7 @@ func (s *sshClient) Tunnel(ctx context.Context, workspace, sandboxName string, p initFrame := &pb.TcpForwardFrame{ Payload: &pb.TcpForwardFrame_Init{ Init: &pb.TcpForwardInit{ - SandboxId: sandbox.ID, + SandboxRef: sandboxReferenceByID(sandbox.ID), ServiceId: cfg.serviceID, AuthorizationToken: session.Token, Target: &pb.TcpForwardInit_Ssh{ diff --git a/sdk/go/openshell/v1/ssh_client_test.go b/sdk/go/openshell/v1/ssh_client_test.go index f1d0516ca..f87427190 100644 --- a/sdk/go/openshell/v1/ssh_client_test.go +++ b/sdk/go/openshell/v1/ssh_client_test.go @@ -51,13 +51,14 @@ func (s *mockSSHServer) CreateSshSession(_ context.Context, req *pb.CreateSshSes return nil, s.createErr } - token := "tok-" + req.GetSandboxId() + sandboxID := sandboxRefID(req.GetSandboxRef()) + token := "tok-" + sandboxID if s.nextToken != "" { token = s.nextToken } resp := &pb.CreateSshSessionResponse{ - SandboxId: req.GetSandboxId(), + SandboxId: sandboxID, Token: token, GatewayHost: "gw.example.com", GatewayPort: 2222, @@ -65,7 +66,7 @@ func (s *mockSSHServer) CreateSshSession(_ context.Context, req *pb.CreateSshSes HostKeyFingerprint: "SHA256:abc123", ExpiresAtMs: 1700000000000, } - s.sessions[req.GetSandboxId()] = resp + s.sessions[sandboxID] = resp s.tokens[token] = true return resp, nil } @@ -330,7 +331,7 @@ func TestSSHTunnel_Success(t *testing.T) { mock.mu.Unlock() require.NotNil(t, init) - assert.Equal(t, "sb-123", init.GetSandboxId()) + assert.Equal(t, "sb-123", init.GetSandboxRef().GetId()) assert.NotEmpty(t, init.GetAuthorizationToken()) assert.NotNil(t, init.GetSsh(), "target should be SshRelayTarget") } diff --git a/sdk/go/openshell/v1/tcp_client.go b/sdk/go/openshell/v1/tcp_client.go index e0795a968..9f51287a4 100644 --- a/sdk/go/openshell/v1/tcp_client.go +++ b/sdk/go/openshell/v1/tcp_client.go @@ -56,8 +56,8 @@ func (t *tcpClient) Forward(ctx context.Context, workspace, sandboxName string, initFrame := &pb.TcpForwardFrame{ Payload: &pb.TcpForwardFrame_Init{ Init: &pb.TcpForwardInit{ - SandboxId: sb.ID, - ServiceId: cfg.serviceID, + SandboxRef: sandboxReferenceByID(sb.ID), + ServiceId: cfg.serviceID, Target: &pb.TcpForwardInit_Tcp{ Tcp: &pb.TcpRelayTarget{ Host: "127.0.0.1", diff --git a/sdk/go/openshell/v1/tcp_client_test.go b/sdk/go/openshell/v1/tcp_client_test.go index 100a746cc..728dbf115 100644 --- a/sdk/go/openshell/v1/tcp_client_test.go +++ b/sdk/go/openshell/v1/tcp_client_test.go @@ -130,7 +130,7 @@ func TestTCPForward_InitFrame(t *testing.T) { mock.mu.Unlock() require.NotNil(t, init) - assert.Equal(t, "sb-my-sandbox", init.GetSandboxId()) + assert.Equal(t, "sb-my-sandbox", init.GetSandboxRef().GetId()) assert.Empty(t, init.GetServiceId(), "service_id should be empty per FR-007a") assert.Empty(t, init.GetAuthorizationToken()) @@ -336,7 +336,7 @@ func TestTCPForward_WithServiceID(t *testing.T) { require.NotNil(t, init) assert.Equal(t, "audit-svc", init.GetServiceId()) - assert.Equal(t, "sb-my-sandbox", init.GetSandboxId()) + assert.Equal(t, "sb-my-sandbox", init.GetSandboxRef().GetId()) } func TestTCPForward_WithoutOptions_BackwardCompat(t *testing.T) { @@ -416,7 +416,7 @@ func TestTCPForward_ResolvesNameToID(t *testing.T) { require.NotNil(t, init) // stubSandboxResolver returns ID "sb-" — verify the proto has the resolved ID, not the name - assert.Equal(t, "sb-my-sandbox", init.GetSandboxId(), "Forward should send resolved sandbox ID, not the name") + assert.Equal(t, "sb-my-sandbox", init.GetSandboxRef().GetId(), "Forward should send resolved sandbox ID, not the name") } func TestTCPForward_ResolutionError(t *testing.T) { diff --git a/sdk/go/openshell/v1/workspace_scope.go b/sdk/go/openshell/v1/workspace_scope.go index 0ae2e8d52..a52c880ed 100644 --- a/sdk/go/openshell/v1/workspace_scope.go +++ b/sdk/go/openshell/v1/workspace_scope.go @@ -16,3 +16,16 @@ func allWorkspacesScope() *dm.WorkspaceSelector { Selection: &dm.WorkspaceSelector_AllWorkspaces{AllWorkspaces: &dm.AllWorkspaces{}}, } } + +func sandboxReferenceByName(workspace, name string) *dm.SandboxReference { + return &dm.SandboxReference{ + Identifier: &dm.SandboxReference_Name{Name: name}, + WorkspaceScope: namedWorkspaceScope(workspace), + } +} + +func sandboxReferenceByID(id string) *dm.SandboxReference { + return &dm.SandboxReference{ + Identifier: &dm.SandboxReference_Id{Id: id}, + } +} diff --git a/sdk/go/proto/datamodelv1/datamodel.pb.go b/sdk/go/proto/datamodelv1/datamodel.pb.go index 1b9eec16b..0df470532 100644 --- a/sdk/go/proto/datamodelv1/datamodel.pb.go +++ b/sdk/go/proto/datamodelv1/datamodel.pb.go @@ -204,6 +204,102 @@ func (*AllWorkspaces) Descriptor() ([]byte, []int) { return file_datamodel_proto_rawDescGZIP(), []int{1} } +// Identifies one existing sandbox in the public API. +// +// Exactly one identifier is required. `id` is the immutable, globally unique +// gateway-generated ObjectMeta.id and MUST NOT include workspace_scope. `name` +// is the human-readable ObjectMeta.name and requires a named workspace_scope; +// all_workspaces is never valid for a sandbox reference. +type SandboxReference struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Identifier: + // + // *SandboxReference_Id + // *SandboxReference_Name + Identifier isSandboxReference_Identifier `protobuf_oneof:"identifier"` + WorkspaceScope *WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxReference) Reset() { + *x = SandboxReference{} + mi := &file_datamodel_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxReference) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxReference) ProtoMessage() {} + +func (x *SandboxReference) ProtoReflect() protoreflect.Message { + mi := &file_datamodel_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxReference.ProtoReflect.Descriptor instead. +func (*SandboxReference) Descriptor() ([]byte, []int) { + return file_datamodel_proto_rawDescGZIP(), []int{2} +} + +func (x *SandboxReference) GetIdentifier() isSandboxReference_Identifier { + if x != nil { + return x.Identifier + } + return nil +} + +func (x *SandboxReference) GetId() string { + if x != nil { + if x, ok := x.Identifier.(*SandboxReference_Id); ok { + return x.Id + } + } + return "" +} + +func (x *SandboxReference) GetName() string { + if x != nil { + if x, ok := x.Identifier.(*SandboxReference_Name); ok { + return x.Name + } + } + return "" +} + +func (x *SandboxReference) GetWorkspaceScope() *WorkspaceSelector { + if x != nil { + return x.WorkspaceScope + } + return nil +} + +type isSandboxReference_Identifier interface { + isSandboxReference_Identifier() +} + +type SandboxReference_Id struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3,oneof"` +} + +type SandboxReference_Name struct { + Name string `protobuf:"bytes,2,opt,name=name,proto3,oneof"` +} + +func (*SandboxReference_Id) isSandboxReference_Identifier() {} + +func (*SandboxReference_Name) isSandboxReference_Identifier() {} + // Kubernetes-style metadata shared by all top-level OpenShell domain objects. // // This structure provides consistent metadata (identity, labels, annotations, @@ -239,7 +335,7 @@ type ObjectMeta struct { func (x *ObjectMeta) Reset() { *x = ObjectMeta{} - mi := &file_datamodel_proto_msgTypes[2] + mi := &file_datamodel_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -251,7 +347,7 @@ func (x *ObjectMeta) String() string { func (*ObjectMeta) ProtoMessage() {} func (x *ObjectMeta) ProtoReflect() protoreflect.Message { - mi := &file_datamodel_proto_msgTypes[2] + mi := &file_datamodel_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -264,7 +360,7 @@ func (x *ObjectMeta) ProtoReflect() protoreflect.Message { // Deprecated: Use ObjectMeta.ProtoReflect.Descriptor instead. func (*ObjectMeta) Descriptor() ([]byte, []int) { - return file_datamodel_proto_rawDescGZIP(), []int{2} + return file_datamodel_proto_rawDescGZIP(), []int{3} } func (x *ObjectMeta) GetId() string { @@ -333,7 +429,7 @@ type WorkspaceStatus struct { func (x *WorkspaceStatus) Reset() { *x = WorkspaceStatus{} - mi := &file_datamodel_proto_msgTypes[3] + mi := &file_datamodel_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -345,7 +441,7 @@ func (x *WorkspaceStatus) String() string { func (*WorkspaceStatus) ProtoMessage() {} func (x *WorkspaceStatus) ProtoReflect() protoreflect.Message { - mi := &file_datamodel_proto_msgTypes[3] + mi := &file_datamodel_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -358,7 +454,7 @@ func (x *WorkspaceStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspaceStatus.ProtoReflect.Descriptor instead. func (*WorkspaceStatus) Descriptor() ([]byte, []int) { - return file_datamodel_proto_rawDescGZIP(), []int{3} + return file_datamodel_proto_rawDescGZIP(), []int{4} } func (x *WorkspaceStatus) GetPhase() WorkspacePhase { @@ -384,7 +480,7 @@ type Workspace struct { func (x *Workspace) Reset() { *x = Workspace{} - mi := &file_datamodel_proto_msgTypes[4] + mi := &file_datamodel_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -396,7 +492,7 @@ func (x *Workspace) String() string { func (*Workspace) ProtoMessage() {} func (x *Workspace) ProtoReflect() protoreflect.Message { - mi := &file_datamodel_proto_msgTypes[4] + mi := &file_datamodel_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -409,7 +505,7 @@ func (x *Workspace) ProtoReflect() protoreflect.Message { // Deprecated: Use Workspace.ProtoReflect.Descriptor instead. func (*Workspace) Descriptor() ([]byte, []int) { - return file_datamodel_proto_rawDescGZIP(), []int{4} + return file_datamodel_proto_rawDescGZIP(), []int{5} } func (x *Workspace) GetMetadata() *ObjectMeta { @@ -442,7 +538,7 @@ type CredentialHandle struct { func (x *CredentialHandle) Reset() { *x = CredentialHandle{} - mi := &file_datamodel_proto_msgTypes[5] + mi := &file_datamodel_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -454,7 +550,7 @@ func (x *CredentialHandle) String() string { func (*CredentialHandle) ProtoMessage() {} func (x *CredentialHandle) ProtoReflect() protoreflect.Message { - mi := &file_datamodel_proto_msgTypes[5] + mi := &file_datamodel_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -467,7 +563,7 @@ func (x *CredentialHandle) ProtoReflect() protoreflect.Message { // Deprecated: Use CredentialHandle.ProtoReflect.Descriptor instead. func (*CredentialHandle) Descriptor() ([]byte, []int) { - return file_datamodel_proto_rawDescGZIP(), []int{5} + return file_datamodel_proto_rawDescGZIP(), []int{6} } func (x *CredentialHandle) GetDriver() string { @@ -518,7 +614,7 @@ type Provider struct { func (x *Provider) Reset() { *x = Provider{} - mi := &file_datamodel_proto_msgTypes[6] + mi := &file_datamodel_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -530,7 +626,7 @@ func (x *Provider) String() string { func (*Provider) ProtoMessage() {} func (x *Provider) ProtoReflect() protoreflect.Message { - mi := &file_datamodel_proto_msgTypes[6] + mi := &file_datamodel_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -543,7 +639,7 @@ func (x *Provider) ProtoReflect() protoreflect.Message { // Deprecated: Use Provider.ProtoReflect.Descriptor instead. func (*Provider) Descriptor() ([]byte, []int) { - return file_datamodel_proto_rawDescGZIP(), []int{6} + return file_datamodel_proto_rawDescGZIP(), []int{7} } func (x *Provider) GetMetadata() *ObjectMeta { @@ -604,7 +700,13 @@ const file_datamodel_proto_rawDesc = "" + "\tworkspace\x18\x01 \x01(\tH\x00R\tworkspace\x12N\n" + "\x0eall_workspaces\x18\x02 \x01(\v2%.openshell.datamodel.v1.AllWorkspacesH\x00R\rallWorkspacesB\v\n" + "\tselection\"\x0f\n" + - "\rAllWorkspaces\"\xeb\x03\n" + + "\rAllWorkspaces\"\x9c\x01\n" + + "\x10SandboxReference\x12\x10\n" + + "\x02id\x18\x01 \x01(\tH\x00R\x02id\x12\x14\n" + + "\x04name\x18\x02 \x01(\tH\x00R\x04name\x12R\n" + + "\x0fworkspace_scope\x18\x03 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeB\f\n" + + "\n" + + "identifier\"\xeb\x03\n" + "\n" + "ObjectMeta\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + @@ -671,43 +773,45 @@ func file_datamodel_proto_rawDescGZIP() []byte { } var file_datamodel_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_datamodel_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_datamodel_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_datamodel_proto_goTypes = []any{ (WorkspacePhase)(0), // 0: openshell.datamodel.v1.WorkspacePhase (*WorkspaceSelector)(nil), // 1: openshell.datamodel.v1.WorkspaceSelector (*AllWorkspaces)(nil), // 2: openshell.datamodel.v1.AllWorkspaces - (*ObjectMeta)(nil), // 3: openshell.datamodel.v1.ObjectMeta - (*WorkspaceStatus)(nil), // 4: openshell.datamodel.v1.WorkspaceStatus - (*Workspace)(nil), // 5: openshell.datamodel.v1.Workspace - (*CredentialHandle)(nil), // 6: openshell.datamodel.v1.CredentialHandle - (*Provider)(nil), // 7: openshell.datamodel.v1.Provider - nil, // 8: openshell.datamodel.v1.ObjectMeta.LabelsEntry - nil, // 9: openshell.datamodel.v1.ObjectMeta.AnnotationsEntry - nil, // 10: openshell.datamodel.v1.CredentialHandle.MetadataEntry - nil, // 11: openshell.datamodel.v1.Provider.CredentialsEntry - nil, // 12: openshell.datamodel.v1.Provider.ConfigEntry - nil, // 13: openshell.datamodel.v1.Provider.CredentialExpiresAtMsEntry - nil, // 14: openshell.datamodel.v1.Provider.CredentialHandlesEntry + (*SandboxReference)(nil), // 3: openshell.datamodel.v1.SandboxReference + (*ObjectMeta)(nil), // 4: openshell.datamodel.v1.ObjectMeta + (*WorkspaceStatus)(nil), // 5: openshell.datamodel.v1.WorkspaceStatus + (*Workspace)(nil), // 6: openshell.datamodel.v1.Workspace + (*CredentialHandle)(nil), // 7: openshell.datamodel.v1.CredentialHandle + (*Provider)(nil), // 8: openshell.datamodel.v1.Provider + nil, // 9: openshell.datamodel.v1.ObjectMeta.LabelsEntry + nil, // 10: openshell.datamodel.v1.ObjectMeta.AnnotationsEntry + nil, // 11: openshell.datamodel.v1.CredentialHandle.MetadataEntry + nil, // 12: openshell.datamodel.v1.Provider.CredentialsEntry + nil, // 13: openshell.datamodel.v1.Provider.ConfigEntry + nil, // 14: openshell.datamodel.v1.Provider.CredentialExpiresAtMsEntry + nil, // 15: openshell.datamodel.v1.Provider.CredentialHandlesEntry } var file_datamodel_proto_depIdxs = []int32{ 2, // 0: openshell.datamodel.v1.WorkspaceSelector.all_workspaces:type_name -> openshell.datamodel.v1.AllWorkspaces - 8, // 1: openshell.datamodel.v1.ObjectMeta.labels:type_name -> openshell.datamodel.v1.ObjectMeta.LabelsEntry - 9, // 2: openshell.datamodel.v1.ObjectMeta.annotations:type_name -> openshell.datamodel.v1.ObjectMeta.AnnotationsEntry - 0, // 3: openshell.datamodel.v1.WorkspaceStatus.phase:type_name -> openshell.datamodel.v1.WorkspacePhase - 3, // 4: openshell.datamodel.v1.Workspace.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 4, // 5: openshell.datamodel.v1.Workspace.status:type_name -> openshell.datamodel.v1.WorkspaceStatus - 10, // 6: openshell.datamodel.v1.CredentialHandle.metadata:type_name -> openshell.datamodel.v1.CredentialHandle.MetadataEntry - 3, // 7: openshell.datamodel.v1.Provider.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 11, // 8: openshell.datamodel.v1.Provider.credentials:type_name -> openshell.datamodel.v1.Provider.CredentialsEntry - 12, // 9: openshell.datamodel.v1.Provider.config:type_name -> openshell.datamodel.v1.Provider.ConfigEntry - 13, // 10: openshell.datamodel.v1.Provider.credential_expires_at_ms:type_name -> openshell.datamodel.v1.Provider.CredentialExpiresAtMsEntry - 14, // 11: openshell.datamodel.v1.Provider.credential_handles:type_name -> openshell.datamodel.v1.Provider.CredentialHandlesEntry - 6, // 12: openshell.datamodel.v1.Provider.CredentialHandlesEntry.value:type_name -> openshell.datamodel.v1.CredentialHandle - 13, // [13:13] is the sub-list for method output_type - 13, // [13:13] is the sub-list for method input_type - 13, // [13:13] is the sub-list for extension type_name - 13, // [13:13] is the sub-list for extension extendee - 0, // [0:13] is the sub-list for field type_name + 1, // 1: openshell.datamodel.v1.SandboxReference.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 9, // 2: openshell.datamodel.v1.ObjectMeta.labels:type_name -> openshell.datamodel.v1.ObjectMeta.LabelsEntry + 10, // 3: openshell.datamodel.v1.ObjectMeta.annotations:type_name -> openshell.datamodel.v1.ObjectMeta.AnnotationsEntry + 0, // 4: openshell.datamodel.v1.WorkspaceStatus.phase:type_name -> openshell.datamodel.v1.WorkspacePhase + 4, // 5: openshell.datamodel.v1.Workspace.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 5, // 6: openshell.datamodel.v1.Workspace.status:type_name -> openshell.datamodel.v1.WorkspaceStatus + 11, // 7: openshell.datamodel.v1.CredentialHandle.metadata:type_name -> openshell.datamodel.v1.CredentialHandle.MetadataEntry + 4, // 8: openshell.datamodel.v1.Provider.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 12, // 9: openshell.datamodel.v1.Provider.credentials:type_name -> openshell.datamodel.v1.Provider.CredentialsEntry + 13, // 10: openshell.datamodel.v1.Provider.config:type_name -> openshell.datamodel.v1.Provider.ConfigEntry + 14, // 11: openshell.datamodel.v1.Provider.credential_expires_at_ms:type_name -> openshell.datamodel.v1.Provider.CredentialExpiresAtMsEntry + 15, // 12: openshell.datamodel.v1.Provider.credential_handles:type_name -> openshell.datamodel.v1.Provider.CredentialHandlesEntry + 7, // 13: openshell.datamodel.v1.Provider.CredentialHandlesEntry.value:type_name -> openshell.datamodel.v1.CredentialHandle + 14, // [14:14] is the sub-list for method output_type + 14, // [14:14] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name } func init() { file_datamodel_proto_init() } @@ -719,13 +823,17 @@ func file_datamodel_proto_init() { (*WorkspaceSelector_Workspace)(nil), (*WorkspaceSelector_AllWorkspaces)(nil), } + file_datamodel_proto_msgTypes[2].OneofWrappers = []any{ + (*SandboxReference_Id)(nil), + (*SandboxReference_Name)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_datamodel_proto_rawDesc), len(file_datamodel_proto_rawDesc)), NumEnums: 1, - NumMessages: 14, + NumMessages: 15, NumExtensions: 0, NumServices: 0, }, diff --git a/sdk/go/proto/openshellv1/openshell.pb.go b/sdk/go/proto/openshellv1/openshell.pb.go index 0979b00b6..273c2fd8f 100644 --- a/sdk/go/proto/openshellv1/openshell.pb.go +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -3073,13 +3073,10 @@ func (x *BeginRootfsTarStagingResponse) GetExpiresAtMs() int64 { // Get sandbox request. type GetSandboxRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). - 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 + state protoimpl.MessageState `protogen:"open.v1"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,4,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetSandboxRequest) Reset() { @@ -3112,16 +3109,9 @@ func (*GetSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{41} } -func (x *GetSandboxRequest) GetName() string { +func (x *GetSandboxRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.Name - } - return "" -} - -func (x *GetSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { - if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -3199,13 +3189,10 @@ func (x *ListSandboxesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelecto // List providers attached to a sandbox request. type ListSandboxProvidersRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). - SandboxName string `protobuf:"bytes,1,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_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 + state protoimpl.MessageState `protogen:"open.v1"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,4,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ListSandboxProvidersRequest) Reset() { @@ -3238,16 +3225,9 @@ func (*ListSandboxProvidersRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{43} } -func (x *ListSandboxProvidersRequest) GetSandboxName() string { +func (x *ListSandboxProvidersRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.SandboxName - } - return "" -} - -func (x *ListSandboxProvidersRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { - if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -3255,19 +3235,16 @@ func (x *ListSandboxProvidersRequest) GetWorkspaceScope() *datamodelv1.Workspace // Attach provider to sandbox request. type AttachSandboxProviderRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). - SandboxName string `protobuf:"bytes,1,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` // Provider name to attach. ProviderName string `protobuf:"bytes,2,opt,name=provider_name,json=providerName,proto3" json:"provider_name,omitempty"` // Expected resource version for optimistic concurrency control. // If 0, the server uses the current version (backward compatibility). // If non-zero, the server validates that the sandbox's current resource_version // matches this value before applying the mutation, returning ABORTED on mismatch. - ExpectedResourceVersion uint64 `protobuf:"varint,3,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` - // Explicit workspace scope. The all-workspaces selection is invalid. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,5,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ExpectedResourceVersion uint64 `protobuf:"varint,3,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,6,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AttachSandboxProviderRequest) Reset() { @@ -3300,13 +3277,6 @@ func (*AttachSandboxProviderRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{44} } -func (x *AttachSandboxProviderRequest) GetSandboxName() string { - if x != nil { - return x.SandboxName - } - return "" -} - func (x *AttachSandboxProviderRequest) GetProviderName() string { if x != nil { return x.ProviderName @@ -3321,9 +3291,9 @@ func (x *AttachSandboxProviderRequest) GetExpectedResourceVersion() uint64 { return 0 } -func (x *AttachSandboxProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *AttachSandboxProviderRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -3331,19 +3301,16 @@ func (x *AttachSandboxProviderRequest) GetWorkspaceScope() *datamodelv1.Workspac // Detach provider from sandbox request. type DetachSandboxProviderRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). - SandboxName string `protobuf:"bytes,1,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` // Provider name to detach. ProviderName string `protobuf:"bytes,2,opt,name=provider_name,json=providerName,proto3" json:"provider_name,omitempty"` // Expected resource version for optimistic concurrency control. // If 0, the server uses the current version (backward compatibility). // If non-zero, the server validates that the sandbox's current resource_version // matches this value before applying the mutation, returning ABORTED on mismatch. - ExpectedResourceVersion uint64 `protobuf:"varint,3,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` - // Explicit workspace scope. The all-workspaces selection is invalid. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,5,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ExpectedResourceVersion uint64 `protobuf:"varint,3,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,6,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DetachSandboxProviderRequest) Reset() { @@ -3376,13 +3343,6 @@ func (*DetachSandboxProviderRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{45} } -func (x *DetachSandboxProviderRequest) GetSandboxName() string { - if x != nil { - return x.SandboxName - } - return "" -} - func (x *DetachSandboxProviderRequest) GetProviderName() string { if x != nil { return x.ProviderName @@ -3397,22 +3357,19 @@ func (x *DetachSandboxProviderRequest) GetExpectedResourceVersion() uint64 { return 0 } -func (x *DetachSandboxProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *DetachSandboxProviderRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } // Delete sandbox request. type DeleteSandboxRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). - 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 + state protoimpl.MessageState `protogen:"open.v1"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,4,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DeleteSandboxRequest) Reset() { @@ -3445,29 +3402,19 @@ func (*DeleteSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{46} } -func (x *DeleteSandboxRequest) GetName() string { +func (x *DeleteSandboxRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.Name - } - return "" -} - -func (x *DeleteSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { - if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } // Stop sandbox request. type StopSandboxRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). - 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 + state protoimpl.MessageState `protogen:"open.v1"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,4,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *StopSandboxRequest) Reset() { @@ -3500,29 +3447,19 @@ func (*StopSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{47} } -func (x *StopSandboxRequest) GetName() string { +func (x *StopSandboxRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.Name - } - return "" -} - -func (x *StopSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { - if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } // Start sandbox request. type StartSandboxRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). - 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 + state protoimpl.MessageState `protogen:"open.v1"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,4,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *StartSandboxRequest) Reset() { @@ -3555,16 +3492,9 @@ func (*StartSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{48} } -func (x *StartSandboxRequest) GetName() string { +func (x *StartSandboxRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.Name - } - return "" -} - -func (x *StartSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { - if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -3859,9 +3789,8 @@ func (x *DeleteSandboxResponse) GetDeleted() bool { // Create SSH session request. type CreateSshSessionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox id. - SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,2,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3896,11 +3825,11 @@ func (*CreateSshSessionRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{55} } -func (x *CreateSshSessionRequest) GetSandboxId() string { +func (x *CreateSshSessionRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.SandboxId + return x.SandboxRef } - return "" + return nil } // Create SSH session response. @@ -4016,18 +3945,15 @@ func (x *CreateSshSessionResponse) GetExpiresAtMs() int64 { // Request to expose an HTTP service running inside a sandbox. type ExposeServiceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name. - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` // Service name within the sandbox. Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` // Loopback TCP port inside the sandbox. TargetPort uint32 `protobuf:"varint,3,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"` // Whether to print/use the browser-facing service URL. - Domain bool `protobuf:"varint,4,opt,name=domain,proto3" json:"domain,omitempty"` - // Explicit workspace scope. The all-workspaces selection is invalid. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,6,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Domain bool `protobuf:"varint,4,opt,name=domain,proto3" json:"domain,omitempty"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,7,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ExposeServiceRequest) Reset() { @@ -4060,13 +3986,6 @@ func (*ExposeServiceRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{57} } -func (x *ExposeServiceRequest) GetSandbox() string { - if x != nil { - return x.Sandbox - } - return "" -} - func (x *ExposeServiceRequest) GetService() string { if x != nil { return x.Service @@ -4088,9 +4007,9 @@ func (x *ExposeServiceRequest) GetDomain() bool { return false } -func (x *ExposeServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ExposeServiceRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -4098,14 +4017,11 @@ func (x *ExposeServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelecto // Request to fetch an exposed sandbox service endpoint. type GetServiceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name. - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` // Service name within the sandbox. Empty selects the unnamed endpoint. - 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"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,5,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetServiceRequest) Reset() { @@ -4138,13 +4054,6 @@ func (*GetServiceRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{58} } -func (x *GetServiceRequest) GetSandbox() string { - if x != nil { - return x.Sandbox - } - return "" -} - func (x *GetServiceRequest) GetService() string { if x != nil { return x.Service @@ -4152,9 +4061,9 @@ func (x *GetServiceRequest) GetService() string { return "" } -func (x *GetServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *GetServiceRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -4162,16 +4071,16 @@ func (x *GetServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { // Request to list exposed sandbox service endpoints. type ListServicesRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Optional sandbox name. Empty lists endpoints for all sandboxes. - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` // Page size. Zero uses the server default. Limit uint32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` // Page offset. Offset uint32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` // Explicit named or all-workspaces scope. WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,6,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Optional sandbox filter. When present, workspace_scope must be omitted. + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,7,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ListServicesRequest) Reset() { @@ -4204,13 +4113,6 @@ func (*ListServicesRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{59} } -func (x *ListServicesRequest) GetSandbox() string { - if x != nil { - return x.Sandbox - } - return "" -} - func (x *ListServicesRequest) GetLimit() uint32 { if x != nil { return x.Limit @@ -4232,6 +4134,13 @@ func (x *ListServicesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector return nil } +func (x *ListServicesRequest) GetSandboxRef() *datamodelv1.SandboxReference { + if x != nil { + return x.SandboxRef + } + return nil +} + // Response containing exposed sandbox service endpoints. type ListServicesResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -4280,14 +4189,11 @@ func (x *ListServicesResponse) GetServices() []*ServiceEndpointResponse { // Request to delete an exposed sandbox service endpoint. type DeleteServiceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name. - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` // Service name within the sandbox. Empty selects the unnamed endpoint. - 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"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,5,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DeleteServiceRequest) Reset() { @@ -4320,13 +4226,6 @@ func (*DeleteServiceRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{61} } -func (x *DeleteServiceRequest) GetSandbox() string { - if x != nil { - return x.Sandbox - } - return "" -} - func (x *DeleteServiceRequest) GetService() string { if x != nil { return x.Service @@ -4334,9 +4233,9 @@ func (x *DeleteServiceRequest) GetService() string { return "" } -func (x *DeleteServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *DeleteServiceRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -4626,8 +4525,6 @@ func (x *RevokeSshSessionResponse) GetRevoked() bool { // Execute command request. type ExecSandboxRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox id. - SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` // Command and arguments. Command []string `protobuf:"bytes,2,rep,name=command,proto3" json:"command,omitempty"` // Optional working directory. @@ -4649,7 +4546,8 @@ type ExecSandboxRequest struct { // (`bash -lc`) so user startup files (.bash_profile/.profile, and .bashrc if // sourced by them) are applied. When true, the command runs without those // files (`bash -c`), for automation that needs predictable startup behavior. - NoLoginShell bool `protobuf:"varint,10,opt,name=no_login_shell,json=noLoginShell,proto3" json:"no_login_shell,omitempty"` + NoLoginShell bool `protobuf:"varint,10,opt,name=no_login_shell,json=noLoginShell,proto3" json:"no_login_shell,omitempty"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,11,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4684,13 +4582,6 @@ func (*ExecSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{67} } -func (x *ExecSandboxRequest) GetSandboxId() string { - if x != nil { - return x.SandboxId - } - return "" -} - func (x *ExecSandboxRequest) GetCommand() []string { if x != nil { return x.Command @@ -4754,6 +4645,13 @@ func (x *ExecSandboxRequest) GetNoLoginShell() bool { return false } +func (x *ExecSandboxRequest) GetSandboxRef() *datamodelv1.SandboxReference { + if x != nil { + return x.SandboxRef + } + return nil +} + // One stdout chunk from a sandbox exec. type ExecSandboxStdout struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -4990,9 +4888,8 @@ func (*ExecSandboxEvent_Exit) isExecSandboxEvent_Payload() {} // Initial frame for one TCP forward stream. type TcpForwardInit struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox id. - SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,2,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` // Optional service identifier for audit/correlation. ServiceId string `protobuf:"bytes,4,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` // Target the gateway should request from the supervisor. @@ -5039,11 +4936,11 @@ func (*TcpForwardInit) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{72} } -func (x *TcpForwardInit) GetSandboxId() string { +func (x *TcpForwardInit) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.SandboxId + return x.SandboxRef } - return "" + return nil } func (x *TcpForwardInit) GetServiceId() string { @@ -5425,8 +5322,6 @@ func (x *SshSession) GetRevoked() bool { // Watch sandbox request. type WatchSandboxRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox id. - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // Stream sandbox status snapshots. FollowStatus bool `protobuf:"varint,2,opt,name=follow_status,json=followStatus,proto3" json:"follow_status,omitempty"` // Stream openshell-server process logs correlated to this sandbox. @@ -5446,7 +5341,8 @@ type WatchSandboxRequest struct { // Filter by log source (e.g. "gateway", "sandbox"). Empty means all sources. LogSources []string `protobuf:"bytes,9,rep,name=log_sources,json=logSources,proto3" json:"log_sources,omitempty"` // Minimum log level to include (e.g. "INFO", "WARN", "ERROR"). Empty means all levels. - LogMinLevel string `protobuf:"bytes,10,opt,name=log_min_level,json=logMinLevel,proto3" json:"log_min_level,omitempty"` + LogMinLevel string `protobuf:"bytes,10,opt,name=log_min_level,json=logMinLevel,proto3" json:"log_min_level,omitempty"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,11,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -5481,13 +5377,6 @@ func (*WatchSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{77} } -func (x *WatchSandboxRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - func (x *WatchSandboxRequest) GetFollowStatus() bool { if x != nil { return x.FollowStatus @@ -5551,6 +5440,13 @@ func (x *WatchSandboxRequest) GetLogMinLevel() string { return "" } +func (x *WatchSandboxRequest) GetSandboxRef() *datamodelv1.SandboxReference { + if x != nil { + return x.SandboxRef + } + return nil +} + // One event in a sandbox watch stream. type SandboxStreamEvent struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -9238,9 +9134,6 @@ func (x *ExchangeProviderSubjectTokenResponse) GetTokenType() string { // Update sandbox policy request. type UpdateConfigRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). Required for sandbox-scoped updates. - // Not required when `global=true`. - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // The new policy to apply. // // Sandbox scope (`global=false`): @@ -9274,11 +9167,10 @@ type UpdateConfigRequest struct { // sandbox metadata as a convenience projection. For setting-only updates, it // only merges them into sandbox metadata. Annotations map[string]string `protobuf:"bytes,9,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - // Explicit workspace scope for sandbox-scoped updates. Omit only when - // `global` is true; the all-workspaces selection is invalid. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,11,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Required for sandbox-scoped updates and omitted for global updates. + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,12,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UpdateConfigRequest) Reset() { @@ -9311,13 +9203,6 @@ func (*UpdateConfigRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{130} } -func (x *UpdateConfigRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - func (x *UpdateConfigRequest) GetPolicy() *sandboxv1.SandboxPolicy { if x != nil { return x.Policy @@ -9374,9 +9259,9 @@ func (x *UpdateConfigRequest) GetAnnotations() map[string]string { return nil } -func (x *UpdateConfigRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *UpdateConfigRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -9940,17 +9825,14 @@ func (x *UpdateConfigResponse) GetAnnotations() map[string]string { // Get sandbox policy status request. type GetSandboxPolicyStatusRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). Ignored when global is true. - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // The specific policy version to query. 0 means latest. Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` // Query global policy revisions instead of a sandbox-scoped one. Global bool `protobuf:"varint,3,opt,name=global,proto3" json:"global,omitempty"` - // Explicit workspace scope for sandbox-scoped queries. Omit only when - // `global` is true; the all-workspaces selection is invalid. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,5,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Required for sandbox-scoped queries and omitted for global queries. + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,6,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetSandboxPolicyStatusRequest) Reset() { @@ -9983,13 +9865,6 @@ func (*GetSandboxPolicyStatusRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{139} } -func (x *GetSandboxPolicyStatusRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - func (x *GetSandboxPolicyStatusRequest) GetVersion() uint32 { if x != nil { return x.Version @@ -10004,9 +9879,9 @@ func (x *GetSandboxPolicyStatusRequest) GetGlobal() bool { return false } -func (x *GetSandboxPolicyStatusRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *GetSandboxPolicyStatusRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -10068,18 +9943,15 @@ func (x *GetSandboxPolicyStatusResponse) GetActiveVersion() uint32 { // List sandbox policies request. type ListSandboxPoliciesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). Ignored when global is true. - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Limit uint32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` - Offset uint32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Limit uint32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` // List global policy revisions instead of sandbox-scoped ones. Global bool `protobuf:"varint,4,opt,name=global,proto3" json:"global,omitempty"` - // Explicit workspace scope for sandbox-scoped queries. Omit only when - // `global` is true; the all-workspaces selection is invalid. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,6,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Required for sandbox-scoped queries and omitted for global queries. + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,7,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ListSandboxPoliciesRequest) Reset() { @@ -10112,13 +9984,6 @@ func (*ListSandboxPoliciesRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{141} } -func (x *ListSandboxPoliciesRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - func (x *ListSandboxPoliciesRequest) GetLimit() uint32 { if x != nil { return x.Limit @@ -10140,9 +10005,9 @@ func (x *ListSandboxPoliciesRequest) GetGlobal() bool { return false } -func (x *ListSandboxPoliciesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ListSandboxPoliciesRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -10421,8 +10286,6 @@ func (x *SandboxPolicyRevision) GetProvenance() map[string]string { // Get sandbox logs request (one-shot fetch). type GetSandboxLogsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox id. - SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` // Maximum number of log lines to return. 0 means use default (2000). Lines uint32 `protobuf:"varint,2,opt,name=lines,proto3" json:"lines,omitempty"` // Only include logs with timestamp >= this value (ms since epoch). 0 means no filter. @@ -10430,11 +10293,10 @@ type GetSandboxLogsRequest struct { // Filter by log source (e.g. "gateway", "sandbox"). Empty means all sources. Sources []string `protobuf:"bytes,4,rep,name=sources,proto3" json:"sources,omitempty"` // Minimum log level to include (e.g. "INFO", "WARN", "ERROR"). Empty means all levels. - MinLevel string `protobuf:"bytes,5,opt,name=min_level,json=minLevel,proto3" json:"min_level,omitempty"` - // Explicit workspace scope. The all-workspaces selection is invalid. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,7,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + MinLevel string `protobuf:"bytes,5,opt,name=min_level,json=minLevel,proto3" json:"min_level,omitempty"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,8,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetSandboxLogsRequest) Reset() { @@ -10467,13 +10329,6 @@ func (*GetSandboxLogsRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{146} } -func (x *GetSandboxLogsRequest) GetSandboxId() string { - if x != nil { - return x.SandboxId - } - return "" -} - func (x *GetSandboxLogsRequest) GetLines() uint32 { if x != nil { return x.Lines @@ -10502,9 +10357,9 @@ func (x *GetSandboxLogsRequest) GetMinLevel() string { return "" } -func (x *GetSandboxLogsRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *GetSandboxLogsRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -12504,11 +12359,14 @@ type SubmitPolicyAnalysisRequest struct { // to watch. Other values are treated as agent-style (no dedup) so a new // mode does not silently collapse proposals. AnalysisMode string `protobuf:"bytes,3,opt,name=analysis_mode,json=analysisMode,proto3" json:"analysis_mode,omitempty"` - // Sandbox name. + // Internal sandbox callback name. This request is not part of the public + // SandboxReference contract; the authenticated sandbox principal is the + // authoritative identity. Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` // Anonymous network activity counters. NetworkActivitySummaries []*NetworkActivitySummary `protobuf:"bytes,5,rep,name=network_activity_summaries,json=networkActivitySummaries,proto3" json:"network_activity_summaries,omitempty"` - // Workspace scope. Empty defaults to "default". + // Internal callback workspace. The gateway validates it against the + // authenticated sandbox principal. Workspace string `protobuf:"bytes,6,opt,name=workspace,proto3" json:"workspace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -12663,14 +12521,11 @@ func (x *SubmitPolicyAnalysisResponse) GetAcceptedChunkIds() []string { // Get draft policy for a sandbox. type GetDraftPolicyRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name. - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Optional status filter: "pending", "approved", "rejected", or "" for all. - StatusFilter string `protobuf:"bytes,2,opt,name=status_filter,json=statusFilter,proto3" json:"status_filter,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"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + StatusFilter string `protobuf:"bytes,2,opt,name=status_filter,json=statusFilter,proto3" json:"status_filter,omitempty"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,5,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetDraftPolicyRequest) Reset() { @@ -12703,13 +12558,6 @@ func (*GetDraftPolicyRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{176} } -func (x *GetDraftPolicyRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - func (x *GetDraftPolicyRequest) GetStatusFilter() string { if x != nil { return x.StatusFilter @@ -12717,9 +12565,9 @@ func (x *GetDraftPolicyRequest) GetStatusFilter() string { return "" } -func (x *GetDraftPolicyRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *GetDraftPolicyRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -12799,17 +12647,14 @@ func (x *GetDraftPolicyResponse) GetLastAnalyzedAtMs() int64 { // Approve a single draft chunk. type ApproveDraftChunkRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name. - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Chunk ID to approve. ChunkId string `protobuf:"bytes,2,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,omitempty"` // Token returned with the reviewed PolicyChunk. Approval fails with // FAILED_PRECONDITION if live decision inputs no longer match it. - ReviewToken string `protobuf:"bytes,4,opt,name=review_token,json=reviewToken,proto3" json:"review_token,omitempty"` - // Explicit workspace scope. The all-workspaces selection is invalid. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,5,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ReviewToken string `protobuf:"bytes,4,opt,name=review_token,json=reviewToken,proto3" json:"review_token,omitempty"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,6,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ApproveDraftChunkRequest) Reset() { @@ -12842,13 +12687,6 @@ func (*ApproveDraftChunkRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{178} } -func (x *ApproveDraftChunkRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - func (x *ApproveDraftChunkRequest) GetChunkId() string { if x != nil { return x.ChunkId @@ -12863,9 +12701,9 @@ func (x *ApproveDraftChunkRequest) GetReviewToken() string { return "" } -func (x *ApproveDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ApproveDraftChunkRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -12927,16 +12765,13 @@ func (x *ApproveDraftChunkResponse) GetPolicyHash() string { // Reject a single draft chunk. type RejectDraftChunkRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name. - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Chunk ID to reject. ChunkId string `protobuf:"bytes,2,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,omitempty"` // Optional reason for rejection (fed to LLM context in future analysis). - Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` - // Explicit workspace scope. The all-workspaces selection is invalid. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,5,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,6,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RejectDraftChunkRequest) Reset() { @@ -12969,13 +12804,6 @@ func (*RejectDraftChunkRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{180} } -func (x *RejectDraftChunkRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - func (x *RejectDraftChunkRequest) GetChunkId() string { if x != nil { return x.ChunkId @@ -12990,9 +12818,9 @@ func (x *RejectDraftChunkRequest) GetReason() string { return "" } -func (x *RejectDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *RejectDraftChunkRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -13088,17 +12916,14 @@ func (x *DraftChunkApproval) GetReviewToken() string { type ApproveAllDraftChunksRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name. - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Include chunks with security_notes (default false: skips them). IncludeSecurityFlagged bool `protobuf:"varint,2,opt,name=include_security_flagged,json=includeSecurityFlagged,proto3" json:"include_security_flagged,omitempty"` // Exact reviewed chunks and tokens. The server validates them against one // live snapshot, stages compatible operations in order, and writes once. - Approvals []*DraftChunkApproval `protobuf:"bytes,4,rep,name=approvals,proto3" json:"approvals,omitempty"` - // Explicit workspace scope. The all-workspaces selection is invalid. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,5,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Approvals []*DraftChunkApproval `protobuf:"bytes,4,rep,name=approvals,proto3" json:"approvals,omitempty"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,6,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ApproveAllDraftChunksRequest) Reset() { @@ -13131,13 +12956,6 @@ func (*ApproveAllDraftChunksRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{183} } -func (x *ApproveAllDraftChunksRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - func (x *ApproveAllDraftChunksRequest) GetIncludeSecurityFlagged() bool { if x != nil { return x.IncludeSecurityFlagged @@ -13152,9 +12970,9 @@ func (x *ApproveAllDraftChunksRequest) GetApprovals() []*DraftChunkApproval { return nil } -func (x *ApproveAllDraftChunksRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ApproveAllDraftChunksRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -13235,16 +13053,13 @@ func (x *ApproveAllDraftChunksResponse) GetChunksSkipped() uint32 { // Edit a pending chunk in-place. type EditDraftChunkRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name. - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Chunk ID to edit. ChunkId string `protobuf:"bytes,2,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,omitempty"` // The modified rule (replaces existing proposed_rule). - ProposedRule *sandboxv1.NetworkPolicyRule `protobuf:"bytes,3,opt,name=proposed_rule,json=proposedRule,proto3" json:"proposed_rule,omitempty"` - // Explicit workspace scope. The all-workspaces selection is invalid. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,5,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ProposedRule *sandboxv1.NetworkPolicyRule `protobuf:"bytes,3,opt,name=proposed_rule,json=proposedRule,proto3" json:"proposed_rule,omitempty"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,6,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *EditDraftChunkRequest) Reset() { @@ -13277,13 +13092,6 @@ func (*EditDraftChunkRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{185} } -func (x *EditDraftChunkRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - func (x *EditDraftChunkRequest) GetChunkId() string { if x != nil { return x.ChunkId @@ -13298,9 +13106,9 @@ func (x *EditDraftChunkRequest) GetProposedRule() *sandboxv1.NetworkPolicyRule { return nil } -func (x *EditDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *EditDraftChunkRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -13344,14 +13152,11 @@ func (*EditDraftChunkResponse) Descriptor() ([]byte, []int) { // Reverse an approval (remove merged rule from active policy). type UndoDraftChunkRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name. - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Chunk ID to undo. - ChunkId string `protobuf:"bytes,2,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,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"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ChunkId string `protobuf:"bytes,2,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,omitempty"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,5,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UndoDraftChunkRequest) Reset() { @@ -13384,13 +13189,6 @@ func (*UndoDraftChunkRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{187} } -func (x *UndoDraftChunkRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - func (x *UndoDraftChunkRequest) GetChunkId() string { if x != nil { return x.ChunkId @@ -13398,9 +13196,9 @@ func (x *UndoDraftChunkRequest) GetChunkId() string { return "" } -func (x *UndoDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *UndoDraftChunkRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -13461,13 +13259,10 @@ func (x *UndoDraftChunkResponse) GetPolicyHash() string { // Clear all pending draft chunks for a sandbox. type ClearDraftChunksRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name. - 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 + state protoimpl.MessageState `protogen:"open.v1"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,4,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ClearDraftChunksRequest) Reset() { @@ -13500,16 +13295,9 @@ func (*ClearDraftChunksRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{189} } -func (x *ClearDraftChunksRequest) GetName() string { +func (x *ClearDraftChunksRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.Name - } - return "" -} - -func (x *ClearDraftChunksRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { - if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -13561,13 +13349,10 @@ func (x *ClearDraftChunksResponse) GetChunksCleared() uint32 { // Get decision history for a sandbox's draft policy. type GetDraftHistoryRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name. - 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 + state protoimpl.MessageState `protogen:"open.v1"` + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,4,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetDraftHistoryRequest) Reset() { @@ -13600,16 +13385,9 @@ func (*GetDraftHistoryRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{191} } -func (x *GetDraftHistoryRequest) GetName() string { +func (x *GetDraftHistoryRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.Name - } - return "" -} - -func (x *GetDraftHistoryRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { - if x != nil { - return x.WorkspaceScope + return x.SandboxRef } return nil } @@ -15397,37 +15175,37 @@ const file_openshell_proto_rawDesc = "" + "\vupload_path\x18\x02 \x01(\tR\n" + "uploadPath\x12\x1b\n" + "\tmax_bytes\x18\x03 \x01(\x04R\bmaxBytes\x12\"\n" + - "\rexpires_at_ms\x18\x04 \x01(\x03R\vexpiresAtMs\"\x8c\x01\n" + - "\x11GetSandboxRequest\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\"\xe6\x01\n" + + "\rexpires_at_ms\x18\x04 \x01(\x03R\vexpiresAtMs\"\x92\x01\n" + + "\x11GetSandboxRequest\x12I\n" + + "\vsandbox_ref\x18\x04 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x02\x10\x03J\x04\b\x03\x10\x04R\x04nameR\tworkspaceR\x0fworkspace_scope\"\xe6\x01\n" + "\x14ListSandboxesRequest\x12\x14\n" + "\x05limit\x18\x01 \x01(\rR\x05limit\x12\x16\n" + "\x06offset\x18\x02 \x01(\rR\x06offset\x12%\n" + "\x0elabel_selector\x18\x03 \x01(\tR\rlabelSelector\x12R\n" + - "\x0fworkspace_scope\x18\x06 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x04\x10\x05J\x04\b\x05\x10\x06R\tworkspaceR\x0eall_workspaces\"\xa5\x01\n" + - "\x1bListSandboxProvidersRequest\x12!\n" + - "\fsandbox_name\x18\x01 \x01(\tR\vsandboxName\x12R\n" + - "\x0fworkspace_scope\x18\x03 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x02\x10\x03R\tworkspace\"\x87\x02\n" + - "\x1cAttachSandboxProviderRequest\x12!\n" + - "\fsandbox_name\x18\x01 \x01(\tR\vsandboxName\x12#\n" + + "\x0fworkspace_scope\x18\x06 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x04\x10\x05J\x04\b\x05\x10\x06R\tworkspaceR\x0eall_workspaces\"\xa4\x01\n" + + "\x1bListSandboxProvidersRequest\x12I\n" + + "\vsandbox_ref\x18\x04 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x02\x10\x03J\x04\b\x03\x10\x04R\fsandbox_nameR\tworkspaceR\x0fworkspace_scope\"\x86\x02\n" + + "\x1cAttachSandboxProviderRequest\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\"\x87\x02\n" + - "\x1cDetachSandboxProviderRequest\x12!\n" + - "\fsandbox_name\x18\x01 \x01(\tR\vsandboxName\x12#\n" + + "\x19expected_resource_version\x18\x03 \x01(\x04R\x17expectedResourceVersion\x12I\n" + + "\vsandbox_ref\x18\x06 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x04\x10\x05J\x04\b\x05\x10\x06R\fsandbox_nameR\tworkspaceR\x0fworkspace_scope\"\x86\x02\n" + + "\x1cDetachSandboxProviderRequest\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" + - "\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" + - "\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" + - "\x13StartSandboxRequest\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\"B\n" + + "\x19expected_resource_version\x18\x03 \x01(\x04R\x17expectedResourceVersion\x12I\n" + + "\vsandbox_ref\x18\x06 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x04\x10\x05J\x04\b\x05\x10\x06R\fsandbox_nameR\tworkspaceR\x0fworkspace_scope\"\x95\x01\n" + + "\x14DeleteSandboxRequest\x12I\n" + + "\vsandbox_ref\x18\x04 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x02\x10\x03J\x04\b\x03\x10\x04R\x04nameR\tworkspaceR\x0fworkspace_scope\"\x93\x01\n" + + "\x12StopSandboxRequest\x12I\n" + + "\vsandbox_ref\x18\x04 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x02\x10\x03J\x04\b\x03\x10\x04R\x04nameR\tworkspaceR\x0fworkspace_scope\"\x94\x01\n" + + "\x13StartSandboxRequest\x12I\n" + + "\vsandbox_ref\x18\x04 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x02\x10\x03J\x04\b\x03\x10\x04R\x04nameR\tworkspaceR\x0fworkspace_scope\"B\n" + "\x0fSandboxResponse\x12/\n" + "\asandbox\x18\x01 \x01(\v2\x15.openshell.v1.SandboxR\asandbox\"L\n" + "\x15ListSandboxesResponse\x123\n" + @@ -15441,10 +15219,11 @@ const file_openshell_proto_rawDesc = "" + "\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" + - "\x17CreateSshSessionRequest\x12\x1d\n" + - "\n" + - "sandbox_id\x18\x01 \x01(\tR\tsandboxId\"\x98\x02\n" + + "\adeleted\x18\x01 \x01(\bR\adeleted\"v\n" + + "\x17CreateSshSessionRequest\x12I\n" + + "\vsandbox_ref\x18\x02 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02R\n" + + "sandbox_id\"\x98\x02\n" + "\x18CreateSshSessionResponse\x12\x1d\n" + "\n" + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x1a\n" + @@ -15453,29 +15232,30 @@ const file_openshell_proto_rawDesc = "" + "\fgateway_port\x18\x04 \x01(\rR\vgatewayPort\x12%\n" + "\x0egateway_scheme\x18\x05 \x01(\tR\rgatewayScheme\x120\n" + "\x14host_key_fingerprint\x18\a \x01(\tR\x12hostKeyFingerprint\x12\"\n" + - "\rexpires_at_ms\x18\b \x01(\x03R\vexpiresAtMs\"\xe8\x01\n" + + "\rexpires_at_ms\x18\b \x01(\x03R\vexpiresAtMs\"\xeb\x01\n" + "\x14ExposeServiceRequest\x12\x18\n" + - "\asandbox\x18\x01 \x01(\tR\asandbox\x12\x18\n" + "\aservice\x18\x02 \x01(\tR\aservice\x12\x1f\n" + "\vtarget_port\x18\x03 \x01(\rR\n" + "targetPort\x12\x16\n" + - "\x06domain\x18\x04 \x01(\bR\x06domain\x12R\n" + - "\x0fworkspace_scope\x18\x06 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x05\x10\x06R\tworkspace\"\xac\x01\n" + + "\x06domain\x18\x04 \x01(\bR\x06domain\x12I\n" + + "\vsandbox_ref\x18\a \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x05\x10\x06J\x04\b\x06\x10\aR\asandboxR\tworkspaceR\x0fworkspace_scope\"\xaf\x01\n" + "\x11GetServiceRequest\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\"\xd8\x01\n" + - "\x13ListServicesRequest\x12\x18\n" + - "\asandbox\x18\x01 \x01(\tR\asandbox\x12\x14\n" + + "\aservice\x18\x02 \x01(\tR\aservice\x12I\n" + + "\vsandbox_ref\x18\x05 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x03\x10\x04J\x04\b\x04\x10\x05R\asandboxR\tworkspaceR\x0fworkspace_scope\"\x98\x02\n" + + "\x13ListServicesRequest\x12\x14\n" + "\x05limit\x18\x02 \x01(\rR\x05limit\x12\x16\n" + "\x06offset\x18\x03 \x01(\rR\x06offset\x12R\n" + - "\x0fworkspace_scope\x18\x06 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x04\x10\x05J\x04\b\x05\x10\x06R\tworkspaceR\x0eall_workspaces\"Y\n" + + "\x0fworkspace_scope\x18\x06 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x12I\n" + + "\vsandbox_ref\x18\a \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x04\x10\x05J\x04\b\x05\x10\x06J\x04\b\x01\x10\x02R\tworkspaceR\x0eall_workspacesR\asandbox\"Y\n" + "\x14ListServicesResponse\x12A\n" + - "\bservices\x18\x01 \x03(\v2%.openshell.v1.ServiceEndpointResponseR\bservices\"\xaf\x01\n" + + "\bservices\x18\x01 \x03(\v2%.openshell.v1.ServiceEndpointResponseR\bservices\"\xb2\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" + + "\aservice\x18\x02 \x01(\tR\aservice\x12I\n" + + "\vsandbox_ref\x18\x05 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x03\x10\x04J\x04\b\x04\x10\x05R\asandboxR\tworkspaceR\x0fworkspace_scope\"1\n" + "\x15DeleteServiceResponse\x12\x18\n" + "\adeleted\x18\x01 \x01(\bR\adeleted\"\xef\x01\n" + "\x0fServiceEndpoint\x12>\n" + @@ -15493,10 +15273,8 @@ const file_openshell_proto_rawDesc = "" + "\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\"\x9b\x03\n" + - "\x12ExecSandboxRequest\x12\x1d\n" + - "\n" + - "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x18\n" + + "\arevoked\x18\x01 \x01(\bR\arevoked\"\xd9\x03\n" + + "\x12ExecSandboxRequest\x12\x18\n" + "\acommand\x18\x02 \x03(\tR\acommand\x12\x18\n" + "\aworkdir\x18\x03 \x01(\tR\aworkdir\x12S\n" + "\venvironment\x18\x04 \x03(\v21.openshell.v1.ExecSandboxRequest.EnvironmentEntryR\venvironment\x12'\n" + @@ -15506,10 +15284,13 @@ const file_openshell_proto_rawDesc = "" + "\x04cols\x18\b \x01(\rR\x04cols\x12\x12\n" + "\x04rows\x18\t \x01(\rR\x04rows\x12$\n" + "\x0eno_login_shell\x18\n" + - " \x01(\bR\fnoLoginShell\x1a>\n" + + " \x01(\bR\fnoLoginShell\x12I\n" + + "\vsandbox_ref\x18\v \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRef\x1a>\n" + "\x10EnvironmentEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"'\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x01\x10\x02R\n" + + "sandbox_id\"'\n" + "\x11ExecSandboxStdout\x12\x12\n" + "\x04data\x18\x01 \x01(\fR\x04data\"'\n" + "\x11ExecSandboxStderr\x12\x12\n" + @@ -15520,16 +15301,17 @@ const file_openshell_proto_rawDesc = "" + "\x06stdout\x18\x01 \x01(\v2\x1f.openshell.v1.ExecSandboxStdoutH\x00R\x06stdout\x129\n" + "\x06stderr\x18\x02 \x01(\v2\x1f.openshell.v1.ExecSandboxStderrH\x00R\x06stderr\x123\n" + "\x04exit\x18\x03 \x01(\v2\x1d.openshell.v1.ExecSandboxExitH\x00R\x04exitB\t\n" + - "\apayload\"\xf3\x01\n" + - "\x0eTcpForwardInit\x12\x1d\n" + - "\n" + - "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x1d\n" + + "\apayload\"\xb1\x02\n" + + "\x0eTcpForwardInit\x12I\n" + + "\vsandbox_ref\x18\x02 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRef\x12\x1d\n" + "\n" + "service_id\x18\x04 \x01(\tR\tserviceId\x120\n" + "\x03ssh\x18\x05 \x01(\v2\x1c.openshell.v1.SshRelayTargetH\x00R\x03ssh\x120\n" + "\x03tcp\x18\x06 \x01(\v2\x1c.openshell.v1.TcpRelayTargetH\x00R\x03tcp\x125\n" + "\x13authorization_token\x18\a \x01(\tB\x04\x88\xb5\x18\x01R\x12authorizationTokenB\b\n" + - "\x06target\"f\n" + + "\x06targetJ\x04\b\x01\x10\x02R\n" + + "sandbox_id\"f\n" + "\x0fTcpForwardFrame\x122\n" + "\x04init\x18\x01 \x01(\v2\x1c.openshell.v1.TcpForwardInitH\x00R\x04init\x12\x14\n" + "\x04data\x18\x02 \x01(\fH\x00R\x04dataB\t\n" + @@ -15549,9 +15331,8 @@ const file_openshell_proto_rawDesc = "" + "sandbox_id\x18\x02 \x01(\tR\tsandboxId\x12\x1a\n" + "\x05token\x18\x03 \x01(\tB\x04\x88\xb5\x18\x01R\x05token\x12\"\n" + "\rexpires_at_ms\x18\x04 \x01(\x03R\vexpiresAtMs\x12\x18\n" + - "\arevoked\x18\x05 \x01(\bR\arevoked\"\xe6\x02\n" + - "\x13WatchSandboxRequest\x12\x0e\n" + - "\x02id\x18\x01 \x01(\tR\x02id\x12#\n" + + "\arevoked\x18\x05 \x01(\bR\arevoked\"\xab\x03\n" + + "\x13WatchSandboxRequest\x12#\n" + "\rfollow_status\x18\x02 \x01(\bR\ffollowStatus\x12\x1f\n" + "\vfollow_logs\x18\x03 \x01(\bR\n" + "followLogs\x12#\n" + @@ -15565,7 +15346,9 @@ const file_openshell_proto_rawDesc = "" + "\vlog_sources\x18\t \x03(\tR\n" + "logSources\x12\"\n" + "\rlog_min_level\x18\n" + - " \x01(\tR\vlogMinLevel\"\xcc\x02\n" + + " \x01(\tR\vlogMinLevel\x12I\n" + + "\vsandbox_ref\x18\v \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02R\x02id\"\xcc\x02\n" + "\x12SandboxStreamEvent\x121\n" + "\asandbox\x18\x01 \x01(\v2\x15.openshell.v1.SandboxH\x00R\asandbox\x120\n" + "\x03log\x18\x02 \x01(\v2\x1c.openshell.v1.SandboxLogLineH\x00R\x03log\x123\n" + @@ -15874,9 +15657,8 @@ const file_openshell_proto_rawDesc = "" + "\n" + "expires_in\x18\x02 \x01(\x03R\texpiresIn\x12\x1d\n" + "\n" + - "token_type\x18\x03 \x01(\tR\ttokenType\"\x95\x05\n" + - "\x13UpdateConfigRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12;\n" + + "token_type\x18\x03 \x01(\tR\ttokenType\"\x9b\x05\n" + + "\x13UpdateConfigRequest\x12;\n" + "\x06policy\x18\x02 \x01(\v2#.openshell.sandbox.v1.SandboxPolicyR\x06policy\x12\x1f\n" + "\vsetting_key\x18\x03 \x01(\tR\n" + "settingKey\x12G\n" + @@ -15885,12 +15667,13 @@ const file_openshell_proto_rawDesc = "" + "\x06global\x18\x06 \x01(\bR\x06global\x12M\n" + "\x10merge_operations\x18\a \x03(\v2\".openshell.v1.PolicyMergeOperationR\x0fmergeOperations\x12:\n" + "\x19expected_resource_version\x18\b \x01(\x04R\x17expectedResourceVersion\x12T\n" + - "\vannotations\x18\t \x03(\v22.openshell.v1.UpdateConfigRequest.AnnotationsEntryR\vannotations\x12R\n" + - "\x0fworkspace_scope\x18\v \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x1a>\n" + + "\vannotations\x18\t \x03(\v22.openshell.v1.UpdateConfigRequest.AnnotationsEntryR\vannotations\x12I\n" + + "\vsandbox_ref\x18\f \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRef\x1a>\n" + "\x10AnnotationsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\n" + - "\x10\vR\tworkspace\"\xc7\x03\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x01\x10\x02J\x04\b\n" + + "\x10\vJ\x04\b\v\x10\fR\x04nameR\tworkspaceR\x0fworkspace_scope\"\xc7\x03\n" + "\x14PolicyMergeOperation\x129\n" + "\badd_rule\x18\x01 \x01(\v2\x1c.openshell.v1.AddNetworkRuleH\x00R\aaddRule\x12N\n" + "\x0fremove_endpoint\x18\x02 \x01(\v2#.openshell.v1.RemoveNetworkEndpointH\x00R\x0eremoveEndpoint\x12B\n" + @@ -15931,21 +15714,21 @@ const file_openshell_proto_rawDesc = "" + "\vannotations\x18\x05 \x03(\v23.openshell.v1.UpdateConfigResponse.AnnotationsEntryR\vannotations\x1a>\n" + "\x10AnnotationsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xca\x01\n" + - "\x1dGetSandboxPolicyStatusRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd0\x01\n" + + "\x1dGetSandboxPolicyStatusRequest\x12\x18\n" + "\aversion\x18\x02 \x01(\rR\aversion\x12\x16\n" + - "\x06global\x18\x03 \x01(\bR\x06global\x12R\n" + - "\x0fworkspace_scope\x18\x05 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x04\x10\x05R\tworkspace\"\x88\x01\n" + + "\x06global\x18\x03 \x01(\bR\x06global\x12I\n" + + "\vsandbox_ref\x18\x06 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x04\x10\x05J\x04\b\x05\x10\x06R\x04nameR\tworkspaceR\x0fworkspace_scope\"\x88\x01\n" + "\x1eGetSandboxPolicyStatusResponse\x12?\n" + "\brevision\x18\x01 \x01(\v2#.openshell.v1.SandboxPolicyRevisionR\brevision\x12%\n" + - "\x0eactive_version\x18\x02 \x01(\rR\ractiveVersion\"\xdb\x01\n" + - "\x1aListSandboxPoliciesRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + + "\x0eactive_version\x18\x02 \x01(\rR\ractiveVersion\"\xe1\x01\n" + + "\x1aListSandboxPoliciesRequest\x12\x14\n" + "\x05limit\x18\x02 \x01(\rR\x05limit\x12\x16\n" + "\x06offset\x18\x03 \x01(\rR\x06offset\x12\x16\n" + - "\x06global\x18\x04 \x01(\bR\x06global\x12R\n" + - "\x0fworkspace_scope\x18\x06 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x05\x10\x06R\tworkspace\"`\n" + + "\x06global\x18\x04 \x01(\bR\x06global\x12I\n" + + "\vsandbox_ref\x18\a \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x05\x10\x06J\x04\b\x06\x10\aR\x04nameR\tworkspaceR\x0fworkspace_scope\"`\n" + "\x1bListSandboxPoliciesResponse\x12A\n" + "\trevisions\x18\x01 \x03(\v2#.openshell.v1.SandboxPolicyRevisionR\trevisions\"\xa7\x01\n" + "\x19ReportPolicyStatusRequest\x12\x1d\n" + @@ -15972,15 +15755,15 @@ const file_openshell_proto_rawDesc = "" + "provenance\x1a=\n" + "\x0fProvenanceEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x83\x02\n" + - "\x15GetSandboxLogsRequest\x12\x1d\n" + - "\n" + - "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x84\x02\n" + + "\x15GetSandboxLogsRequest\x12\x14\n" + "\x05lines\x18\x02 \x01(\rR\x05lines\x12\x19\n" + "\bsince_ms\x18\x03 \x01(\x03R\asinceMs\x12\x18\n" + "\asources\x18\x04 \x03(\tR\asources\x12\x1b\n" + - "\tmin_level\x18\x05 \x01(\tR\bminLevel\x12R\n" + - "\x0fworkspace_scope\x18\a \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x06\x10\aR\tworkspace\"i\n" + + "\tmin_level\x18\x05 \x01(\tR\bminLevel\x12I\n" + + "\vsandbox_ref\x18\b \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x06\x10\aJ\x04\b\a\x10\bR\n" + + "sandbox_idR\tworkspaceR\x0fworkspace_scope\"i\n" + "\x16PushSandboxLogsRequest\x12\x1d\n" + "\n" + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x120\n" + @@ -16145,67 +15928,67 @@ const file_openshell_proto_rawDesc = "" + "\x0faccepted_chunks\x18\x01 \x01(\rR\x0eacceptedChunks\x12'\n" + "\x0frejected_chunks\x18\x02 \x01(\rR\x0erejectedChunks\x12+\n" + "\x11rejection_reasons\x18\x03 \x03(\tR\x10rejectionReasons\x12,\n" + - "\x12accepted_chunk_ids\x18\x04 \x03(\tR\x10acceptedChunkIds\"\xb5\x01\n" + - "\x15GetDraftPolicyRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12#\n" + - "\rstatus_filter\x18\x02 \x01(\tR\fstatusFilter\x12R\n" + - "\x0fworkspace_scope\x18\x04 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x03\x10\x04R\tworkspace\"\xc8\x01\n" + + "\x12accepted_chunk_ids\x18\x04 \x03(\tR\x10acceptedChunkIds\"\xbb\x01\n" + + "\x15GetDraftPolicyRequest\x12#\n" + + "\rstatus_filter\x18\x02 \x01(\tR\fstatusFilter\x12I\n" + + "\vsandbox_ref\x18\x05 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x03\x10\x04J\x04\b\x04\x10\x05R\x04nameR\tworkspaceR\x0fworkspace_scope\"\xc8\x01\n" + "\x16GetDraftPolicyResponse\x121\n" + "\x06chunks\x18\x01 \x03(\v2\x19.openshell.v1.PolicyChunkR\x06chunks\x12'\n" + "\x0frolling_summary\x18\x02 \x01(\tR\x0erollingSummary\x12#\n" + "\rdraft_version\x18\x03 \x01(\x04R\fdraftVersion\x12-\n" + - "\x13last_analyzed_at_ms\x18\x04 \x01(\x03R\x10lastAnalyzedAtMs\"\xd1\x01\n" + - "\x18ApproveDraftChunkRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12\x19\n" + + "\x13last_analyzed_at_ms\x18\x04 \x01(\x03R\x10lastAnalyzedAtMs\"\xd7\x01\n" + + "\x18ApproveDraftChunkRequest\x12\x19\n" + "\bchunk_id\x18\x02 \x01(\tR\achunkId\x12!\n" + - "\freview_token\x18\x04 \x01(\tR\vreviewToken\x12R\n" + - "\x0fworkspace_scope\x18\x05 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x03\x10\x04R\tworkspace\"c\n" + + "\freview_token\x18\x04 \x01(\tR\vreviewToken\x12I\n" + + "\vsandbox_ref\x18\x06 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x03\x10\x04J\x04\b\x05\x10\x06R\x04nameR\tworkspaceR\x0fworkspace_scope\"c\n" + "\x19ApproveDraftChunkResponse\x12%\n" + "\x0epolicy_version\x18\x01 \x01(\rR\rpolicyVersion\x12\x1f\n" + "\vpolicy_hash\x18\x02 \x01(\tR\n" + - "policyHash\"\xc5\x01\n" + - "\x17RejectDraftChunkRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12\x19\n" + + "policyHash\"\xcb\x01\n" + + "\x17RejectDraftChunkRequest\x12\x19\n" + "\bchunk_id\x18\x02 \x01(\tR\achunkId\x12\x16\n" + - "\x06reason\x18\x03 \x01(\tR\x06reason\x12R\n" + - "\x0fworkspace_scope\x18\x05 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x04\x10\x05R\tworkspace\"\x1a\n" + + "\x06reason\x18\x03 \x01(\tR\x06reason\x12I\n" + + "\vsandbox_ref\x18\x06 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x04\x10\x05J\x04\b\x05\x10\x06R\x04nameR\tworkspaceR\x0fworkspace_scope\"\x1a\n" + "\x18RejectDraftChunkResponse\"R\n" + "\x12DraftChunkApproval\x12\x19\n" + "\bchunk_id\x18\x01 \x01(\tR\achunkId\x12!\n" + - "\freview_token\x18\x02 \x01(\tR\vreviewToken\"\x91\x02\n" + - "\x1cApproveAllDraftChunksRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x128\n" + + "\freview_token\x18\x02 \x01(\tR\vreviewToken\"\x97\x02\n" + + "\x1cApproveAllDraftChunksRequest\x128\n" + "\x18include_security_flagged\x18\x02 \x01(\bR\x16includeSecurityFlagged\x12>\n" + - "\tapprovals\x18\x04 \x03(\v2 .openshell.v1.DraftChunkApprovalR\tapprovals\x12R\n" + - "\x0fworkspace_scope\x18\x05 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x03\x10\x04R\tworkspace\"\xb7\x01\n" + + "\tapprovals\x18\x04 \x03(\v2 .openshell.v1.DraftChunkApprovalR\tapprovals\x12I\n" + + "\vsandbox_ref\x18\x06 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x03\x10\x04J\x04\b\x05\x10\x06R\x04nameR\tworkspaceR\x0fworkspace_scope\"\xb7\x01\n" + "\x1dApproveAllDraftChunksResponse\x12%\n" + "\x0epolicy_version\x18\x01 \x01(\rR\rpolicyVersion\x12\x1f\n" + "\vpolicy_hash\x18\x02 \x01(\tR\n" + "policyHash\x12'\n" + "\x0fchunks_approved\x18\x03 \x01(\rR\x0echunksApproved\x12%\n" + - "\x0echunks_skipped\x18\x04 \x01(\rR\rchunksSkipped\"\xf9\x01\n" + - "\x15EditDraftChunkRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12\x19\n" + + "\x0echunks_skipped\x18\x04 \x01(\rR\rchunksSkipped\"\xff\x01\n" + + "\x15EditDraftChunkRequest\x12\x19\n" + "\bchunk_id\x18\x02 \x01(\tR\achunkId\x12L\n" + - "\rproposed_rule\x18\x03 \x01(\v2'.openshell.sandbox.v1.NetworkPolicyRuleR\fproposedRule\x12R\n" + - "\x0fworkspace_scope\x18\x05 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x04\x10\x05R\tworkspace\"\x18\n" + - "\x16EditDraftChunkResponse\"\xab\x01\n" + - "\x15UndoDraftChunkRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12\x19\n" + - "\bchunk_id\x18\x02 \x01(\tR\achunkId\x12R\n" + - "\x0fworkspace_scope\x18\x04 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x03\x10\x04R\tworkspace\"`\n" + + "\rproposed_rule\x18\x03 \x01(\v2'.openshell.sandbox.v1.NetworkPolicyRuleR\fproposedRule\x12I\n" + + "\vsandbox_ref\x18\x06 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x04\x10\x05J\x04\b\x05\x10\x06R\x04nameR\tworkspaceR\x0fworkspace_scope\"\x18\n" + + "\x16EditDraftChunkResponse\"\xb1\x01\n" + + "\x15UndoDraftChunkRequest\x12\x19\n" + + "\bchunk_id\x18\x02 \x01(\tR\achunkId\x12I\n" + + "\vsandbox_ref\x18\x05 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x03\x10\x04J\x04\b\x04\x10\x05R\x04nameR\tworkspaceR\x0fworkspace_scope\"`\n" + "\x16UndoDraftChunkResponse\x12%\n" + "\x0epolicy_version\x18\x01 \x01(\rR\rpolicyVersion\x12\x1f\n" + "\vpolicy_hash\x18\x02 \x01(\tR\n" + - "policyHash\"\x92\x01\n" + - "\x17ClearDraftChunksRequest\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\"A\n" + + "policyHash\"\x98\x01\n" + + "\x17ClearDraftChunksRequest\x12I\n" + + "\vsandbox_ref\x18\x04 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x02\x10\x03J\x04\b\x03\x10\x04R\x04nameR\tworkspaceR\x0fworkspace_scope\"A\n" + "\x18ClearDraftChunksResponse\x12%\n" + - "\x0echunks_cleared\x18\x01 \x01(\rR\rchunksCleared\"\x91\x01\n" + - "\x16GetDraftHistoryRequest\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\"\x92\x01\n" + + "\x0echunks_cleared\x18\x01 \x01(\rR\rchunksCleared\"\x97\x01\n" + + "\x16GetDraftHistoryRequest\x12I\n" + + "\vsandbox_ref\x18\x04 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02J\x04\b\x02\x10\x03J\x04\b\x03\x10\x04R\x04nameR\tworkspaceR\x0fworkspace_scope\"\x92\x01\n" + "\x11DraftHistoryEntry\x12!\n" + "\ftimestamp_ms\x18\x01 \x01(\x03R\vtimestampMs\x12\x1d\n" + "\n" + @@ -16827,19 +16610,20 @@ var file_openshell_proto_goTypes = []any{ (*structpb.Struct)(nil), // 250: google.protobuf.Struct (*durationpb.Duration)(nil), // 251: google.protobuf.Duration (*datamodelv1.WorkspaceSelector)(nil), // 252: openshell.datamodel.v1.WorkspaceSelector - (*datamodelv1.Provider)(nil), // 253: openshell.datamodel.v1.Provider - (*datamodelv1.CredentialHandle)(nil), // 254: openshell.datamodel.v1.CredentialHandle - (*sandboxv1.NetworkEndpoint)(nil), // 255: openshell.sandbox.v1.NetworkEndpoint - (*sandboxv1.NetworkBinary)(nil), // 256: openshell.sandbox.v1.NetworkBinary - (*sandboxv1.SettingValue)(nil), // 257: openshell.sandbox.v1.SettingValue - (*sandboxv1.NetworkPolicyRule)(nil), // 258: openshell.sandbox.v1.NetworkPolicyRule - (*sandboxv1.L7DenyRule)(nil), // 259: openshell.sandbox.v1.L7DenyRule - (*sandboxv1.L7Rule)(nil), // 260: openshell.sandbox.v1.L7Rule - (*datamodelv1.Workspace)(nil), // 261: openshell.datamodel.v1.Workspace - (*sandboxv1.GetSandboxConfigRequest)(nil), // 262: openshell.sandbox.v1.GetSandboxConfigRequest - (*sandboxv1.GetGatewayConfigRequest)(nil), // 263: openshell.sandbox.v1.GetGatewayConfigRequest - (*sandboxv1.GetSandboxConfigResponse)(nil), // 264: openshell.sandbox.v1.GetSandboxConfigResponse - (*sandboxv1.GetGatewayConfigResponse)(nil), // 265: openshell.sandbox.v1.GetGatewayConfigResponse + (*datamodelv1.SandboxReference)(nil), // 253: openshell.datamodel.v1.SandboxReference + (*datamodelv1.Provider)(nil), // 254: openshell.datamodel.v1.Provider + (*datamodelv1.CredentialHandle)(nil), // 255: openshell.datamodel.v1.CredentialHandle + (*sandboxv1.NetworkEndpoint)(nil), // 256: openshell.sandbox.v1.NetworkEndpoint + (*sandboxv1.NetworkBinary)(nil), // 257: openshell.sandbox.v1.NetworkBinary + (*sandboxv1.SettingValue)(nil), // 258: openshell.sandbox.v1.SettingValue + (*sandboxv1.NetworkPolicyRule)(nil), // 259: openshell.sandbox.v1.NetworkPolicyRule + (*sandboxv1.L7DenyRule)(nil), // 260: openshell.sandbox.v1.L7DenyRule + (*sandboxv1.L7Rule)(nil), // 261: openshell.sandbox.v1.L7Rule + (*datamodelv1.Workspace)(nil), // 262: openshell.datamodel.v1.Workspace + (*sandboxv1.GetSandboxConfigRequest)(nil), // 263: openshell.sandbox.v1.GetSandboxConfigRequest + (*sandboxv1.GetGatewayConfigRequest)(nil), // 264: openshell.sandbox.v1.GetGatewayConfigRequest + (*sandboxv1.GetSandboxConfigResponse)(nil), // 265: openshell.sandbox.v1.GetSandboxConfigResponse + (*sandboxv1.GetGatewayConfigResponse)(nil), // 266: openshell.sandbox.v1.GetGatewayConfigResponse } var file_openshell_proto_depIdxs = []int32{ 221, // 0: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential @@ -16890,334 +16674,339 @@ var file_openshell_proto_depIdxs = []int32{ 29, // 45: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate 29, // 46: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate 252, // 47: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 48: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 253, // 48: openshell.v1.GetSandboxRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference 252, // 49: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 50: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 51: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 52: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 53: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 54: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 55: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 253, // 50: openshell.v1.ListSandboxProvidersRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 253, // 51: openshell.v1.AttachSandboxProviderRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 253, // 52: openshell.v1.DetachSandboxProviderRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 253, // 53: openshell.v1.DeleteSandboxRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 253, // 54: openshell.v1.StopSandboxRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 253, // 55: openshell.v1.StartSandboxRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference 24, // 56: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox 24, // 57: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox - 253, // 58: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 254, // 58: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider 24, // 59: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox 24, // 60: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 252, // 61: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 62: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 63: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 72, // 64: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse - 252, // 65: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 248, // 66: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 71, // 67: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint - 230, // 68: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry - 76, // 69: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout - 77, // 70: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr - 78, // 71: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit - 170, // 72: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget - 171, // 73: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget - 80, // 74: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit - 75, // 75: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest - 83, // 76: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize - 248, // 77: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 24, // 78: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox - 87, // 79: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine - 38, // 80: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent - 88, // 81: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning - 181, // 82: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate - 231, // 83: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry - 253, // 84: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 252, // 85: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 86: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 87: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 253, // 88: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 232, // 89: openshell.v1.UpdateProviderRequest.credential_expires_at_ms:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry - 252, // 90: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 91: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 253, // 92: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider - 253, // 93: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 119, // 94: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile - 100, // 95: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride - 1, // 96: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType - 101, // 97: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken - 106, // 98: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh - 102, // 99: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant - 2, // 100: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 104, // 101: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial - 105, // 102: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput - 2, // 103: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 7, // 104: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 248, // 105: openshell.v1.StoredProviderCredentialRefreshState.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 2, // 106: openshell.v1.StoredProviderCredentialRefreshState.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 233, // 107: openshell.v1.StoredProviderCredentialRefreshState.material:type_name -> openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry - 234, // 108: openshell.v1.StoredProviderCredentialRefreshState.additional_output_keys:type_name -> openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry - 235, // 109: openshell.v1.StoredProviderCredentialRefreshState.secret_material_handles:type_name -> openshell.v1.StoredProviderCredentialRefreshState.SecretMaterialHandlesEntry - 110, // 110: openshell.v1.StoredProviderCredentialRefreshState.pending_secret_deletions:type_name -> openshell.v1.StoredRefreshMaterialDeletion - 7, // 111: openshell.v1.StoredProviderCredentialRefreshState.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 254, // 112: openshell.v1.StoredRefreshMaterialDeletion.handle:type_name -> openshell.datamodel.v1.CredentialHandle - 252, // 113: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 107, // 114: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 2, // 115: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 236, // 116: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - 252, // 117: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 107, // 118: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 252, // 119: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 107, // 120: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 252, // 121: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 3, // 122: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory - 103, // 123: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential - 255, // 124: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint - 256, // 125: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 108, // 126: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery - 237, // 127: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry - 248, // 128: openshell.v1.StoredProviderProfile.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 119, // 129: openshell.v1.StoredProviderProfile.profile:type_name -> openshell.v1.ProviderProfile - 119, // 130: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile - 119, // 131: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 98, // 132: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 99, // 133: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 119, // 134: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 98, // 135: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem - 99, // 136: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 119, // 137: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile - 98, // 138: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 99, // 139: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 133, // 140: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding - 238, // 141: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - 239, // 142: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expires_at_ms:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry - 240, // 143: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - 241, // 144: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - 249, // 145: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 257, // 146: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue - 139, // 147: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation - 242, // 148: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry - 252, // 149: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 140, // 150: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule - 141, // 151: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint - 142, // 152: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule - 143, // 153: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules - 144, // 154: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules - 145, // 155: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary - 258, // 156: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 259, // 157: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule - 260, // 158: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule - 243, // 159: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry - 252, // 160: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 153, // 161: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision - 252, // 162: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 153, // 163: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision - 4, // 164: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus - 4, // 165: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus - 249, // 166: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 244, // 167: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry - 252, // 168: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 87, // 169: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine - 87, // 170: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine - 160, // 171: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello - 163, // 172: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat - 174, // 173: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult - 175, // 174: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose - 161, // 175: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted - 162, // 176: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected - 164, // 177: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat - 169, // 178: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen - 175, // 179: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose - 170, // 180: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget - 171, // 181: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget - 172, // 182: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit - 176, // 183: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample - 178, // 184: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount - 258, // 185: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 249, // 186: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 249, // 187: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 177, // 188: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary - 180, // 189: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk - 179, // 190: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary - 252, // 191: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 180, // 192: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk - 252, // 193: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 194: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 190, // 195: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval - 252, // 196: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 258, // 197: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 252, // 198: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 199: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 200: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 252, // 201: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 200, // 202: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry - 249, // 203: openshell.v1.PolicyRevisionPayload.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 245, // 204: openshell.v1.PolicyRevisionPayload.provenance:type_name -> openshell.v1.PolicyRevisionPayload.ProvenanceEntry - 258, // 205: openshell.v1.DraftChunkPayload.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 249, // 206: openshell.v1.DraftChunkPayload.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 249, // 207: openshell.v1.DraftChunkPayload.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 246, // 208: openshell.v1.StoredPolicyRevision.provenance:type_name -> openshell.v1.StoredPolicyRevision.ProvenanceEntry - 249, // 209: openshell.v1.StoredDraftChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 249, // 210: openshell.v1.StoredDraftChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 247, // 211: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry - 261, // 212: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 261, // 213: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 261, // 214: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace - 248, // 215: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 6, // 216: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole - 6, // 217: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole - 214, // 218: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember - 214, // 219: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember - 254, // 220: openshell.v1.StoredProviderCredentialRefreshState.SecretMaterialHandlesEntry.value:type_name -> openshell.datamodel.v1.CredentialHandle - 103, // 221: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 134, // 222: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding - 12, // 223: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest - 14, // 224: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest - 16, // 225: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest - 39, // 226: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest - 47, // 227: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest - 49, // 228: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest - 50, // 229: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest - 40, // 230: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest - 41, // 231: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest - 42, // 232: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest - 43, // 233: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest - 51, // 234: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest - 52, // 235: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest - 53, // 236: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest - 54, // 237: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest - 55, // 238: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest - 56, // 239: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest - 63, // 240: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest - 65, // 241: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest - 66, // 242: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest - 67, // 243: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest - 69, // 244: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest - 73, // 245: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest - 75, // 246: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest - 81, // 247: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame - 82, // 248: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput - 89, // 249: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest - 90, // 250: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest - 91, // 251: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest - 96, // 252: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest - 97, // 253: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest - 123, // 254: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest - 125, // 255: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest - 127, // 256: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest - 92, // 257: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest - 111, // 258: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest - 113, // 259: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest - 115, // 260: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest - 117, // 261: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest - 93, // 262: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest - 130, // 263: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest - 262, // 264: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest - 263, // 265: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest - 138, // 266: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest - 147, // 267: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest - 149, // 268: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest - 151, // 269: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest - 132, // 270: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest - 136, // 271: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest - 154, // 272: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest - 155, // 273: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest - 158, // 274: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage - 165, // 275: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest - 167, // 276: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest - 173, // 277: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame - 85, // 278: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest - 182, // 279: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest - 184, // 280: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest - 186, // 281: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest - 188, // 282: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest - 191, // 283: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest - 193, // 284: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest - 195, // 285: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest - 197, // 286: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest - 199, // 287: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest - 8, // 288: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest - 10, // 289: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest - 206, // 290: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest - 208, // 291: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest - 210, // 292: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest - 212, // 293: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest - 215, // 294: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest - 217, // 295: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest - 219, // 296: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest - 13, // 297: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse - 15, // 298: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse - 17, // 299: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse - 57, // 300: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse - 48, // 301: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse - 57, // 302: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse - 58, // 303: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse - 44, // 304: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 44, // 305: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 45, // 306: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse - 46, // 307: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse - 59, // 308: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse - 60, // 309: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse - 61, // 310: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse - 62, // 311: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse - 57, // 312: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse - 57, // 313: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse - 64, // 314: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse - 72, // 315: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse - 72, // 316: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse - 68, // 317: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse - 70, // 318: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse - 74, // 319: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse - 79, // 320: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent - 81, // 321: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame - 79, // 322: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent - 94, // 323: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse - 94, // 324: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse - 95, // 325: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse - 122, // 326: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse - 121, // 327: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse - 124, // 328: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse - 126, // 329: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse - 128, // 330: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse - 94, // 331: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse - 112, // 332: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse - 114, // 333: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse - 116, // 334: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse - 118, // 335: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse - 129, // 336: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse - 131, // 337: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse - 264, // 338: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse - 265, // 339: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse - 146, // 340: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse - 148, // 341: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse - 150, // 342: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse - 152, // 343: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse - 135, // 344: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse - 137, // 345: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse - 157, // 346: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse - 156, // 347: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse - 159, // 348: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage - 166, // 349: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse - 168, // 350: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse - 173, // 351: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame - 86, // 352: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent - 183, // 353: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse - 185, // 354: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse - 187, // 355: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse - 189, // 356: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse - 192, // 357: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse - 194, // 358: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse - 196, // 359: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse - 198, // 360: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse - 201, // 361: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse - 9, // 362: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse - 11, // 363: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse - 207, // 364: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse - 209, // 365: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse - 211, // 366: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse - 213, // 367: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse - 216, // 368: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse - 218, // 369: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse - 220, // 370: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse - 297, // [297:371] is the sub-list for method output_type - 223, // [223:297] is the sub-list for method input_type - 223, // [223:223] is the sub-list for extension type_name - 223, // [223:223] is the sub-list for extension extendee - 0, // [0:223] is the sub-list for field type_name + 253, // 61: openshell.v1.CreateSshSessionRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 253, // 62: openshell.v1.ExposeServiceRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 253, // 63: openshell.v1.GetServiceRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 252, // 64: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 253, // 65: openshell.v1.ListServicesRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 72, // 66: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse + 253, // 67: openshell.v1.DeleteServiceRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 248, // 68: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 71, // 69: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint + 230, // 70: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry + 253, // 71: openshell.v1.ExecSandboxRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 76, // 72: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout + 77, // 73: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr + 78, // 74: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit + 253, // 75: openshell.v1.TcpForwardInit.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 170, // 76: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget + 171, // 77: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget + 80, // 78: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit + 75, // 79: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest + 83, // 80: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize + 248, // 81: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 253, // 82: openshell.v1.WatchSandboxRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 24, // 83: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox + 87, // 84: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine + 38, // 85: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent + 88, // 86: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning + 181, // 87: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate + 231, // 88: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry + 254, // 89: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 252, // 90: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 252, // 91: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 252, // 92: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 254, // 93: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 232, // 94: openshell.v1.UpdateProviderRequest.credential_expires_at_ms:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry + 252, // 95: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 252, // 96: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 254, // 97: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider + 254, // 98: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 119, // 99: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile + 100, // 100: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride + 1, // 101: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType + 101, // 102: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken + 106, // 103: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh + 102, // 104: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant + 2, // 105: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 104, // 106: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial + 105, // 107: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput + 2, // 108: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 7, // 109: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction + 248, // 110: openshell.v1.StoredProviderCredentialRefreshState.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 2, // 111: openshell.v1.StoredProviderCredentialRefreshState.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 233, // 112: openshell.v1.StoredProviderCredentialRefreshState.material:type_name -> openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry + 234, // 113: openshell.v1.StoredProviderCredentialRefreshState.additional_output_keys:type_name -> openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry + 235, // 114: openshell.v1.StoredProviderCredentialRefreshState.secret_material_handles:type_name -> openshell.v1.StoredProviderCredentialRefreshState.SecretMaterialHandlesEntry + 110, // 115: openshell.v1.StoredProviderCredentialRefreshState.pending_secret_deletions:type_name -> openshell.v1.StoredRefreshMaterialDeletion + 7, // 116: openshell.v1.StoredProviderCredentialRefreshState.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction + 255, // 117: openshell.v1.StoredRefreshMaterialDeletion.handle:type_name -> openshell.datamodel.v1.CredentialHandle + 252, // 118: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 107, // 119: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 2, // 120: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 236, // 121: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + 252, // 122: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 107, // 123: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 252, // 124: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 107, // 125: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 252, // 126: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 3, // 127: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory + 103, // 128: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential + 256, // 129: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 257, // 130: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 108, // 131: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery + 237, // 132: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry + 248, // 133: openshell.v1.StoredProviderProfile.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 119, // 134: openshell.v1.StoredProviderProfile.profile:type_name -> openshell.v1.ProviderProfile + 119, // 135: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile + 119, // 136: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 98, // 137: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 99, // 138: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 119, // 139: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 98, // 140: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem + 99, // 141: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 119, // 142: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile + 98, // 143: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 99, // 144: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 133, // 145: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding + 238, // 146: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + 239, // 147: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expires_at_ms:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry + 240, // 148: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + 241, // 149: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + 249, // 150: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 258, // 151: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue + 139, // 152: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation + 242, // 153: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry + 253, // 154: openshell.v1.UpdateConfigRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 140, // 155: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule + 141, // 156: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint + 142, // 157: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule + 143, // 158: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules + 144, // 159: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules + 145, // 160: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary + 259, // 161: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 260, // 162: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 261, // 163: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule + 243, // 164: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry + 253, // 165: openshell.v1.GetSandboxPolicyStatusRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 153, // 166: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision + 253, // 167: openshell.v1.ListSandboxPoliciesRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 153, // 168: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision + 4, // 169: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus + 4, // 170: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus + 249, // 171: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 244, // 172: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry + 253, // 173: openshell.v1.GetSandboxLogsRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 87, // 174: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine + 87, // 175: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine + 160, // 176: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello + 163, // 177: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat + 174, // 178: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult + 175, // 179: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose + 161, // 180: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted + 162, // 181: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected + 164, // 182: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat + 169, // 183: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen + 175, // 184: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose + 170, // 185: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget + 171, // 186: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget + 172, // 187: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit + 176, // 188: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample + 178, // 189: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount + 259, // 190: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 249, // 191: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 249, // 192: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 177, // 193: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary + 180, // 194: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk + 179, // 195: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary + 253, // 196: openshell.v1.GetDraftPolicyRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 180, // 197: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk + 253, // 198: openshell.v1.ApproveDraftChunkRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 253, // 199: openshell.v1.RejectDraftChunkRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 190, // 200: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval + 253, // 201: openshell.v1.ApproveAllDraftChunksRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 259, // 202: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 253, // 203: openshell.v1.EditDraftChunkRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 253, // 204: openshell.v1.UndoDraftChunkRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 253, // 205: openshell.v1.ClearDraftChunksRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 253, // 206: openshell.v1.GetDraftHistoryRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 200, // 207: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry + 249, // 208: openshell.v1.PolicyRevisionPayload.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 245, // 209: openshell.v1.PolicyRevisionPayload.provenance:type_name -> openshell.v1.PolicyRevisionPayload.ProvenanceEntry + 259, // 210: openshell.v1.DraftChunkPayload.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 249, // 211: openshell.v1.DraftChunkPayload.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 249, // 212: openshell.v1.DraftChunkPayload.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 246, // 213: openshell.v1.StoredPolicyRevision.provenance:type_name -> openshell.v1.StoredPolicyRevision.ProvenanceEntry + 249, // 214: openshell.v1.StoredDraftChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 249, // 215: openshell.v1.StoredDraftChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 247, // 216: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry + 262, // 217: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 262, // 218: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 262, // 219: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace + 248, // 220: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 6, // 221: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole + 6, // 222: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole + 214, // 223: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember + 214, // 224: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember + 255, // 225: openshell.v1.StoredProviderCredentialRefreshState.SecretMaterialHandlesEntry.value:type_name -> openshell.datamodel.v1.CredentialHandle + 103, // 226: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 134, // 227: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding + 12, // 228: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest + 14, // 229: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest + 16, // 230: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest + 39, // 231: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest + 47, // 232: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest + 49, // 233: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest + 50, // 234: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest + 40, // 235: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest + 41, // 236: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest + 42, // 237: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest + 43, // 238: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest + 51, // 239: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest + 52, // 240: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest + 53, // 241: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest + 54, // 242: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest + 55, // 243: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest + 56, // 244: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest + 63, // 245: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest + 65, // 246: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest + 66, // 247: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest + 67, // 248: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest + 69, // 249: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest + 73, // 250: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest + 75, // 251: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest + 81, // 252: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame + 82, // 253: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput + 89, // 254: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest + 90, // 255: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest + 91, // 256: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest + 96, // 257: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest + 97, // 258: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest + 123, // 259: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest + 125, // 260: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest + 127, // 261: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest + 92, // 262: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest + 111, // 263: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest + 113, // 264: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest + 115, // 265: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest + 117, // 266: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest + 93, // 267: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest + 130, // 268: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest + 263, // 269: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest + 264, // 270: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest + 138, // 271: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest + 147, // 272: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest + 149, // 273: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest + 151, // 274: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest + 132, // 275: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest + 136, // 276: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest + 154, // 277: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest + 155, // 278: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest + 158, // 279: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage + 165, // 280: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest + 167, // 281: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest + 173, // 282: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame + 85, // 283: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest + 182, // 284: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest + 184, // 285: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest + 186, // 286: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest + 188, // 287: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest + 191, // 288: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest + 193, // 289: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest + 195, // 290: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest + 197, // 291: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest + 199, // 292: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest + 8, // 293: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest + 10, // 294: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest + 206, // 295: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest + 208, // 296: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest + 210, // 297: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest + 212, // 298: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest + 215, // 299: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest + 217, // 300: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest + 219, // 301: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest + 13, // 302: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse + 15, // 303: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse + 17, // 304: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse + 57, // 305: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse + 48, // 306: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse + 57, // 307: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse + 58, // 308: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse + 44, // 309: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 44, // 310: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 45, // 311: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse + 46, // 312: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse + 59, // 313: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse + 60, // 314: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse + 61, // 315: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse + 62, // 316: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse + 57, // 317: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse + 57, // 318: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse + 64, // 319: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse + 72, // 320: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse + 72, // 321: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse + 68, // 322: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse + 70, // 323: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse + 74, // 324: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse + 79, // 325: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent + 81, // 326: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame + 79, // 327: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent + 94, // 328: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse + 94, // 329: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse + 95, // 330: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse + 122, // 331: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse + 121, // 332: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse + 124, // 333: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse + 126, // 334: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse + 128, // 335: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse + 94, // 336: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse + 112, // 337: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse + 114, // 338: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse + 116, // 339: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse + 118, // 340: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse + 129, // 341: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse + 131, // 342: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse + 265, // 343: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse + 266, // 344: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse + 146, // 345: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse + 148, // 346: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse + 150, // 347: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse + 152, // 348: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse + 135, // 349: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse + 137, // 350: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse + 157, // 351: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse + 156, // 352: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse + 159, // 353: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage + 166, // 354: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse + 168, // 355: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse + 173, // 356: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame + 86, // 357: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent + 183, // 358: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse + 185, // 359: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse + 187, // 360: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse + 189, // 361: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse + 192, // 362: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse + 194, // 363: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse + 196, // 364: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse + 198, // 365: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse + 201, // 366: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse + 9, // 367: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse + 11, // 368: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse + 207, // 369: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse + 209, // 370: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse + 211, // 371: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse + 213, // 372: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse + 216, // 373: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse + 218, // 374: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse + 220, // 375: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse + 302, // [302:376] is the sub-list for method output_type + 228, // [228:302] is the sub-list for method input_type + 228, // [228:228] is the sub-list for extension type_name + 228, // [228:228] is the sub-list for extension extendee + 0, // [0:228] is the sub-list for field type_name } func init() { file_openshell_proto_init() } diff --git a/sdk/go/proto/sandboxv1/sandbox.pb.go b/sdk/go/proto/sandboxv1/sandbox.pb.go index 989589002..094ed020a 100644 --- a/sdk/go/proto/sandboxv1/sandbox.pb.go +++ b/sdk/go/proto/sandboxv1/sandbox.pb.go @@ -10,6 +10,7 @@ package sandboxv1 import ( + datamodelv1 "github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" structpb "google.golang.org/protobuf/types/known/structpb" @@ -1497,8 +1498,9 @@ func (x *NetworkBinary) GetHarness() bool { // Request to get sandbox settings by sandbox ID. type GetSandboxConfigRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // The sandbox ID. - SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Canonical public sandbox reference. Sandbox-authenticated callers use the + // ID variant; user clients may use either supported variant. + SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,2,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1533,11 +1535,11 @@ func (*GetSandboxConfigRequest) Descriptor() ([]byte, []int) { return file_sandbox_proto_rawDescGZIP(), []int{16} } -func (x *GetSandboxConfigRequest) GetSandboxId() string { +func (x *GetSandboxConfigRequest) GetSandboxRef() *datamodelv1.SandboxReference { if x != nil { - return x.SandboxId + return x.SandboxRef } - return "" + return nil } // Request to get gateway-global settings. @@ -2069,7 +2071,7 @@ var File_sandbox_proto protoreflect.FileDescriptor const file_sandbox_proto_rawDesc = "" + "\n" + - "\rsandbox.proto\x12\x14openshell.sandbox.v1\x1a\x1cgoogle/protobuf/struct.proto\"\xa8\x05\n" + + "\rsandbox.proto\x12\x14openshell.sandbox.v1\x1a\x0fdatamodel.proto\x1a\x1cgoogle/protobuf/struct.proto\"\xa8\x05\n" + "\rSandboxPolicy\x12\x18\n" + "\aversion\x18\x01 \x01(\rR\aversion\x12F\n" + "\n" + @@ -2199,10 +2201,11 @@ const file_sandbox_proto_rawDesc = "" + "\x03any\x18\x02 \x03(\tR\x03any\"A\n" + "\rNetworkBinary\x12\x12\n" + "\x04path\x18\x01 \x01(\tR\x04path\x12\x1c\n" + - "\aharness\x18\x02 \x01(\bB\x02\x18\x01R\aharness\"8\n" + - "\x17GetSandboxConfigRequest\x12\x1d\n" + - "\n" + - "sandbox_id\x18\x01 \x01(\tR\tsandboxId\"\x19\n" + + "\aharness\x18\x02 \x01(\bB\x02\x18\x01R\aharness\"v\n" + + "\x17GetSandboxConfigRequest\x12I\n" + + "\vsandbox_ref\x18\x02 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" + + "sandboxRefJ\x04\b\x01\x10\x02R\n" + + "sandbox_id\"\x19\n" + "\x17GetGatewayConfigRequest\"\x82\x02\n" + "\x18GetGatewayConfigResponse\x12X\n" + "\bsettings\x18\x01 \x03(\v2<.openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntryR\bsettings\x12+\n" + @@ -2271,41 +2274,42 @@ func file_sandbox_proto_rawDescGZIP() []byte { var file_sandbox_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_sandbox_proto_msgTypes = make([]protoimpl.MessageInfo, 32) var file_sandbox_proto_goTypes = []any{ - (SettingScope)(0), // 0: openshell.sandbox.v1.SettingScope - (PolicySource)(0), // 1: openshell.sandbox.v1.PolicySource - (*SandboxPolicy)(nil), // 2: openshell.sandbox.v1.SandboxPolicy - (*FilesystemPolicy)(nil), // 3: openshell.sandbox.v1.FilesystemPolicy - (*LandlockPolicy)(nil), // 4: openshell.sandbox.v1.LandlockPolicy - (*ProcessPolicy)(nil), // 5: openshell.sandbox.v1.ProcessPolicy - (*NetworkPolicyRule)(nil), // 6: openshell.sandbox.v1.NetworkPolicyRule - (*NetworkMiddlewareConfig)(nil), // 7: openshell.sandbox.v1.NetworkMiddlewareConfig - (*MiddlewareEndpointSelector)(nil), // 8: openshell.sandbox.v1.MiddlewareEndpointSelector - (*NetworkCredentialBinding)(nil), // 9: openshell.sandbox.v1.NetworkCredentialBinding - (*NetworkEndpoint)(nil), // 10: openshell.sandbox.v1.NetworkEndpoint - (*McpOptions)(nil), // 11: openshell.sandbox.v1.McpOptions - (*GraphqlOperation)(nil), // 12: openshell.sandbox.v1.GraphqlOperation - (*L7DenyRule)(nil), // 13: openshell.sandbox.v1.L7DenyRule - (*L7Rule)(nil), // 14: openshell.sandbox.v1.L7Rule - (*L7Allow)(nil), // 15: openshell.sandbox.v1.L7Allow - (*L7QueryMatcher)(nil), // 16: openshell.sandbox.v1.L7QueryMatcher - (*NetworkBinary)(nil), // 17: openshell.sandbox.v1.NetworkBinary - (*GetSandboxConfigRequest)(nil), // 18: openshell.sandbox.v1.GetSandboxConfigRequest - (*GetGatewayConfigRequest)(nil), // 19: openshell.sandbox.v1.GetGatewayConfigRequest - (*GetGatewayConfigResponse)(nil), // 20: openshell.sandbox.v1.GetGatewayConfigResponse - (*SettingValue)(nil), // 21: openshell.sandbox.v1.SettingValue - (*EffectiveSetting)(nil), // 22: openshell.sandbox.v1.EffectiveSetting - (*GetSandboxConfigResponse)(nil), // 23: openshell.sandbox.v1.GetSandboxConfigResponse - (*SupervisorMiddlewareService)(nil), // 24: openshell.sandbox.v1.SupervisorMiddlewareService - nil, // 25: openshell.sandbox.v1.SandboxPolicy.NetworkPoliciesEntry - nil, // 26: openshell.sandbox.v1.SandboxPolicy.NetworkMiddlewaresEntry - nil, // 27: openshell.sandbox.v1.NetworkEndpoint.GraphqlPersistedQueriesEntry - nil, // 28: openshell.sandbox.v1.L7DenyRule.QueryEntry - nil, // 29: openshell.sandbox.v1.L7DenyRule.ParamsEntry - nil, // 30: openshell.sandbox.v1.L7Allow.QueryEntry - nil, // 31: openshell.sandbox.v1.L7Allow.ParamsEntry - nil, // 32: openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry - nil, // 33: openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry - (*structpb.Struct)(nil), // 34: google.protobuf.Struct + (SettingScope)(0), // 0: openshell.sandbox.v1.SettingScope + (PolicySource)(0), // 1: openshell.sandbox.v1.PolicySource + (*SandboxPolicy)(nil), // 2: openshell.sandbox.v1.SandboxPolicy + (*FilesystemPolicy)(nil), // 3: openshell.sandbox.v1.FilesystemPolicy + (*LandlockPolicy)(nil), // 4: openshell.sandbox.v1.LandlockPolicy + (*ProcessPolicy)(nil), // 5: openshell.sandbox.v1.ProcessPolicy + (*NetworkPolicyRule)(nil), // 6: openshell.sandbox.v1.NetworkPolicyRule + (*NetworkMiddlewareConfig)(nil), // 7: openshell.sandbox.v1.NetworkMiddlewareConfig + (*MiddlewareEndpointSelector)(nil), // 8: openshell.sandbox.v1.MiddlewareEndpointSelector + (*NetworkCredentialBinding)(nil), // 9: openshell.sandbox.v1.NetworkCredentialBinding + (*NetworkEndpoint)(nil), // 10: openshell.sandbox.v1.NetworkEndpoint + (*McpOptions)(nil), // 11: openshell.sandbox.v1.McpOptions + (*GraphqlOperation)(nil), // 12: openshell.sandbox.v1.GraphqlOperation + (*L7DenyRule)(nil), // 13: openshell.sandbox.v1.L7DenyRule + (*L7Rule)(nil), // 14: openshell.sandbox.v1.L7Rule + (*L7Allow)(nil), // 15: openshell.sandbox.v1.L7Allow + (*L7QueryMatcher)(nil), // 16: openshell.sandbox.v1.L7QueryMatcher + (*NetworkBinary)(nil), // 17: openshell.sandbox.v1.NetworkBinary + (*GetSandboxConfigRequest)(nil), // 18: openshell.sandbox.v1.GetSandboxConfigRequest + (*GetGatewayConfigRequest)(nil), // 19: openshell.sandbox.v1.GetGatewayConfigRequest + (*GetGatewayConfigResponse)(nil), // 20: openshell.sandbox.v1.GetGatewayConfigResponse + (*SettingValue)(nil), // 21: openshell.sandbox.v1.SettingValue + (*EffectiveSetting)(nil), // 22: openshell.sandbox.v1.EffectiveSetting + (*GetSandboxConfigResponse)(nil), // 23: openshell.sandbox.v1.GetSandboxConfigResponse + (*SupervisorMiddlewareService)(nil), // 24: openshell.sandbox.v1.SupervisorMiddlewareService + nil, // 25: openshell.sandbox.v1.SandboxPolicy.NetworkPoliciesEntry + nil, // 26: openshell.sandbox.v1.SandboxPolicy.NetworkMiddlewaresEntry + nil, // 27: openshell.sandbox.v1.NetworkEndpoint.GraphqlPersistedQueriesEntry + nil, // 28: openshell.sandbox.v1.L7DenyRule.QueryEntry + nil, // 29: openshell.sandbox.v1.L7DenyRule.ParamsEntry + nil, // 30: openshell.sandbox.v1.L7Allow.QueryEntry + nil, // 31: openshell.sandbox.v1.L7Allow.ParamsEntry + nil, // 32: openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry + nil, // 33: openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry + (*structpb.Struct)(nil), // 34: google.protobuf.Struct + (*datamodelv1.SandboxReference)(nil), // 35: openshell.datamodel.v1.SandboxReference } var file_sandbox_proto_depIdxs = []int32{ 3, // 0: openshell.sandbox.v1.SandboxPolicy.filesystem:type_name -> openshell.sandbox.v1.FilesystemPolicy @@ -2327,27 +2331,28 @@ var file_sandbox_proto_depIdxs = []int32{ 15, // 16: openshell.sandbox.v1.L7Rule.allow:type_name -> openshell.sandbox.v1.L7Allow 30, // 17: openshell.sandbox.v1.L7Allow.query:type_name -> openshell.sandbox.v1.L7Allow.QueryEntry 31, // 18: openshell.sandbox.v1.L7Allow.params:type_name -> openshell.sandbox.v1.L7Allow.ParamsEntry - 32, // 19: openshell.sandbox.v1.GetGatewayConfigResponse.settings:type_name -> openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry - 21, // 20: openshell.sandbox.v1.EffectiveSetting.value:type_name -> openshell.sandbox.v1.SettingValue - 0, // 21: openshell.sandbox.v1.EffectiveSetting.scope:type_name -> openshell.sandbox.v1.SettingScope - 2, // 22: openshell.sandbox.v1.GetSandboxConfigResponse.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 33, // 23: openshell.sandbox.v1.GetSandboxConfigResponse.settings:type_name -> openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry - 1, // 24: openshell.sandbox.v1.GetSandboxConfigResponse.policy_source:type_name -> openshell.sandbox.v1.PolicySource - 24, // 25: openshell.sandbox.v1.GetSandboxConfigResponse.supervisor_middleware_services:type_name -> openshell.sandbox.v1.SupervisorMiddlewareService - 6, // 26: openshell.sandbox.v1.SandboxPolicy.NetworkPoliciesEntry.value:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 7, // 27: openshell.sandbox.v1.SandboxPolicy.NetworkMiddlewaresEntry.value:type_name -> openshell.sandbox.v1.NetworkMiddlewareConfig - 12, // 28: openshell.sandbox.v1.NetworkEndpoint.GraphqlPersistedQueriesEntry.value:type_name -> openshell.sandbox.v1.GraphqlOperation - 16, // 29: openshell.sandbox.v1.L7DenyRule.QueryEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher - 16, // 30: openshell.sandbox.v1.L7DenyRule.ParamsEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher - 16, // 31: openshell.sandbox.v1.L7Allow.QueryEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher - 16, // 32: openshell.sandbox.v1.L7Allow.ParamsEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher - 21, // 33: openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry.value:type_name -> openshell.sandbox.v1.SettingValue - 22, // 34: openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry.value:type_name -> openshell.sandbox.v1.EffectiveSetting - 35, // [35:35] is the sub-list for method output_type - 35, // [35:35] is the sub-list for method input_type - 35, // [35:35] is the sub-list for extension type_name - 35, // [35:35] is the sub-list for extension extendee - 0, // [0:35] is the sub-list for field type_name + 35, // 19: openshell.sandbox.v1.GetSandboxConfigRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference + 32, // 20: openshell.sandbox.v1.GetGatewayConfigResponse.settings:type_name -> openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry + 21, // 21: openshell.sandbox.v1.EffectiveSetting.value:type_name -> openshell.sandbox.v1.SettingValue + 0, // 22: openshell.sandbox.v1.EffectiveSetting.scope:type_name -> openshell.sandbox.v1.SettingScope + 2, // 23: openshell.sandbox.v1.GetSandboxConfigResponse.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 33, // 24: openshell.sandbox.v1.GetSandboxConfigResponse.settings:type_name -> openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry + 1, // 25: openshell.sandbox.v1.GetSandboxConfigResponse.policy_source:type_name -> openshell.sandbox.v1.PolicySource + 24, // 26: openshell.sandbox.v1.GetSandboxConfigResponse.supervisor_middleware_services:type_name -> openshell.sandbox.v1.SupervisorMiddlewareService + 6, // 27: openshell.sandbox.v1.SandboxPolicy.NetworkPoliciesEntry.value:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 7, // 28: openshell.sandbox.v1.SandboxPolicy.NetworkMiddlewaresEntry.value:type_name -> openshell.sandbox.v1.NetworkMiddlewareConfig + 12, // 29: openshell.sandbox.v1.NetworkEndpoint.GraphqlPersistedQueriesEntry.value:type_name -> openshell.sandbox.v1.GraphqlOperation + 16, // 30: openshell.sandbox.v1.L7DenyRule.QueryEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher + 16, // 31: openshell.sandbox.v1.L7DenyRule.ParamsEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher + 16, // 32: openshell.sandbox.v1.L7Allow.QueryEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher + 16, // 33: openshell.sandbox.v1.L7Allow.ParamsEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher + 21, // 34: openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry.value:type_name -> openshell.sandbox.v1.SettingValue + 22, // 35: openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry.value:type_name -> openshell.sandbox.v1.EffectiveSetting + 36, // [36:36] is the sub-list for method output_type + 36, // [36:36] is the sub-list for method input_type + 36, // [36:36] is the sub-list for extension type_name + 36, // [36:36] is the sub-list for extension extendee + 0, // [0:36] is the sub-list for field type_name } func init() { file_sandbox_proto_init() } diff --git a/sdk/typescript/src/client.test.ts b/sdk/typescript/src/client.test.ts index 60cd5c3f0..d33add16e 100644 --- a/sdk/typescript/src/client.test.ts +++ b/sdk/typescript/src/client.test.ts @@ -58,20 +58,34 @@ const enc = (s: string) => new TextEncoder().encode(s); type ScopedRequest = { workspaceScope?: { selection?: { case?: string; value?: unknown } }; + sandboxRef?: { + identifier?: { case?: string; value?: unknown }; + workspaceScope?: { selection?: { case?: string; value?: unknown } }; + }; }; function selectedWorkspace(req: ScopedRequest): string | undefined { - const selection = req.workspaceScope?.selection; + const selection = (req.sandboxRef?.workspaceScope ?? req.workspaceScope)?.selection; return selection?.case === 'workspace' && typeof selection.value === 'string' ? selection.value : undefined; } +function requestSandboxName(req: ScopedRequest): string | undefined { + const identifier = req.sandboxRef?.identifier; + return identifier?.case === 'name' && typeof identifier.value === 'string' ? identifier.value : undefined; +} + +function requestSandboxId(req: ScopedRequest): string | undefined { + const identifier = req.sandboxRef?.identifier; + return identifier?.case === 'id' && typeof identifier.value === 'string' ? identifier.value : undefined; +} + function selectsAllWorkspaces(req: ScopedRequest): boolean { return req.workspaceScope?.selection?.case === 'allWorkspaces'; } describe('exec / execStream', () => { it('resolves the id via get, frames tty:false, and buffers the result (backward compat)', async () => { - let execReq: { sandboxId?: string; tty?: boolean; command?: string[] } = {}; + let execReq: ScopedRequest & { tty?: boolean; command?: string[] } = {}; const sandbox = client({ getSandbox: () => readySandbox('sb', 'sb-id-1'), // eslint-disable-next-line require-yield @@ -85,7 +99,7 @@ describe('exec / execStream', () => { }); const result = await sandbox.exec('sb', ['/bin/sh', '-c', 'echo hi']); - expect(execReq.sandboxId).toBe('sb-id-1'); + expect(requestSandboxId(execReq)).toBe('sb-id-1'); expect(execReq.tty).toBe(false); expect(execReq.command).toEqual(['/bin/sh', '-c', 'echo hi']); expect(result.exitCode).toBe(3); @@ -352,13 +366,14 @@ describe('create', () => { }, getSandbox: (req) => { const workspace = selectedWorkspace(req); - if (req.name === 'exec') observed.execGet = workspace; - else if (req.name === 'interactive') observed.interactiveGet = workspace; - else if (req.name === 'ssh') observed.sshGet = workspace; - else if (req.name === 'forward') observed.forwardGet = workspace; - else if (req.name === 'config' && workspace) observed.configGets.push(workspace); + const name = requestSandboxName(req); + if (name === 'exec') observed.execGet = workspace; + else if (name === 'interactive') observed.interactiveGet = workspace; + else if (name === 'ssh') observed.sshGet = workspace; + else if (name === 'forward') observed.forwardGet = workspace; + else if (name === 'config' && workspace) observed.configGets.push(workspace); else observed.get = req; - return readySandbox(req.name, `${req.name}-id`, 7n, undefined, workspace ?? 'default'); + return readySandbox(name ?? '', `${name}-id`, 7n, undefined, workspace ?? 'default'); }, listSandboxes: (req) => { observed.list = req; @@ -384,16 +399,26 @@ describe('create', () => { attachSandboxProvider: (req) => { observed.attach = req; return { - sandbox: readySandbox(req.sandboxName, 'attach-id', 7n, undefined, selectedWorkspace(req) ?? 'default') - .sandbox, + sandbox: readySandbox( + requestSandboxName(req) ?? '', + 'attach-id', + 7n, + undefined, + selectedWorkspace(req) ?? 'default', + ).sandbox, attached: true, }; }, detachSandboxProvider: (req) => { observed.detach = req; return { - sandbox: readySandbox(req.sandboxName, 'detach-id', 7n, undefined, selectedWorkspace(req) ?? 'default') - .sandbox, + sandbox: readySandbox( + requestSandboxName(req) ?? '', + 'detach-id', + 7n, + undefined, + selectedWorkspace(req) ?? 'default', + ).sandbox, detached: true, }; }, @@ -425,7 +450,7 @@ describe('create', () => { yield { payload: { case: 'exit', value: { exitCode: 0 } } }; }, createSshSession: (req) => ({ - sandboxId: req.sandboxId, + sandboxId: requestSandboxId(req) ?? '', token: 'tok', gatewayHost: 'gw', gatewayPort: 443, @@ -756,7 +781,7 @@ describe('Pushable', () => { describe('execInteractive', () => { it('sends start first with tty/cols/rows, streams output, and resolves done', async () => { const cases: string[] = []; - let started: { tty?: boolean; cols?: number; rows?: number; sandboxId?: string } | undefined; + let started: (ScopedRequest & { tty?: boolean; cols?: number; rows?: number }) | undefined; const sandbox = client({ getSandbox: () => readySandbox('sb', 'sb-id-9'), execSandboxInteractive: async function* (requests) { @@ -800,7 +825,7 @@ describe('execInteractive', () => { expect(started?.tty).toBe(true); expect(started?.cols).toBe(120); expect(started?.rows).toBe(40); - expect(started?.sandboxId).toBe('sb-id-9'); + expect(requestSandboxId(started ?? {})).toBe('sb-id-9'); expect(out.join('')).toContain('ready\n'); expect(out.join('')).toContain('echo hi'); }); @@ -864,7 +889,7 @@ describe('exec done settlement', () => { describe('providers', () => { it('attach/detach assemble the request and map the changed flag + sandbox ref', async () => { let attachReq: { - sandboxName?: string; + sandboxRef?: ScopedRequest['sandboxRef']; providerName?: string; expectedResourceVersion?: bigint; } = {}; @@ -884,7 +909,7 @@ describe('providers', () => { }); const attach = await sandbox.attachProvider('sb', 'claude'); - expect(attachReq.sandboxName).toBe('sb'); + expect(requestSandboxName(attachReq)).toBe('sb'); expect(attachReq.providerName).toBe('claude'); expect(attachReq.expectedResourceVersion).toBe(0n); expect(attach.changed).toBe(true); @@ -961,7 +986,7 @@ describe('config / policy', () => { it('setPolicy sends global=false + version pin and (wait) polls until the hash matches', async () => { let updateReq: { - name?: string; + sandboxRef?: ScopedRequest['sandboxRef']; global?: boolean; expectedResourceVersion?: bigint; policy?: unknown; @@ -1002,7 +1027,7 @@ describe('config / policy', () => { }, { wait: true, expectedResourceVersion: '7' }, ); - expect(updateReq.name).toBe('sb'); + expect(requestSandboxName(updateReq)).toBe('sb'); expect(updateReq.global).toBe(false); expect(updateReq.expectedResourceVersion).toBe(7n); expect(updateReq.policy).toBeDefined(); @@ -1035,7 +1060,7 @@ describe('config / policy', () => { it('setSetting upserts a single sandbox-scoped setting (global=false)', async () => { let req: { - name?: string; + sandboxRef?: ScopedRequest['sandboxRef']; settingKey?: string; global?: boolean; settingValue?: unknown; @@ -1054,7 +1079,7 @@ describe('config / policy', () => { const result = await sandbox.setSetting('sb', 'feature.enabled', { value: { case: 'boolValue', value: true }, }); - expect(req.name).toBe('sb'); + expect(requestSandboxName(req)).toBe('sb'); expect(req.settingKey).toBe('feature.enabled'); expect(req.global).toBe(false); expect(req.settingValue).toMatchObject({ @@ -1171,9 +1196,9 @@ describe('ssh sessions', () => { describe('forward', () => { it('binds a local port and relays bytes both ways, minting + revoking a token', async () => { - let sshReq: { sandboxId?: string } = {}; + let sshReq: ScopedRequest = {}; let revokedToken: string | undefined; - let initFrame: { sandboxId?: string; authorizationToken?: string; target?: unknown } | undefined; + let initFrame: (ScopedRequest & { authorizationToken?: string; target?: unknown }) | undefined; const sandbox = client({ getSandbox: () => readySandbox('sb', 'sb-id-forward'), createSshSession: (req) => { @@ -1224,8 +1249,8 @@ describe('forward', () => { }); expect(echoed).toBe('ping-through-forward'); - expect(sshReq.sandboxId).toBe('sb-id-forward'); - expect(initFrame?.sandboxId).toBe('sb-id-forward'); + expect(requestSandboxId(sshReq)).toBe('sb-id-forward'); + expect(requestSandboxId(initFrame ?? {})).toBe('sb-id-forward'); expect(initFrame?.authorizationToken).toBe('fwd-tok'); expect(initFrame?.target).toMatchObject({ case: 'tcp', @@ -1469,7 +1494,12 @@ describe('raw escape hatch', () => { // raw returns the full generated message: the enum stays numeric, where the // curated get() would lowercase status.phase to 'ready'. - const resp = await sandbox.raw.getSandbox({ name: 'sb' }); + const resp = await sandbox.raw.getSandbox({ + sandboxRef: { + identifier: { case: 'name', value: 'sb' }, + workspaceScope: { selection: { case: 'workspace', value: 'default' } }, + }, + }); expect(resp.sandbox?.status?.phase).toBe(SandboxPhase.READY); expect(resp.sandbox?.metadata?.name).toBe('sb'); diff --git a/sdk/typescript/src/client.ts b/sdk/typescript/src/client.ts index 839a425e2..ddfee557d 100644 --- a/sdk/typescript/src/client.ts +++ b/sdk/typescript/src/client.ts @@ -506,6 +506,17 @@ function listWorkspaceScope(options?: WorkspaceListScope | null): MessageInitSha return options?.allWorkspaces ? { selection: { case: 'allWorkspaces', value: {} } } : workspaceScope(options); } +function sandboxReferenceByName(name: string, options?: SandboxWorkspaceOptions | null) { + return { + identifier: { case: 'name' as const, value: name }, + workspaceScope: workspaceScope(options), + }; +} + +function sandboxReferenceById(id: string) { + return { identifier: { case: 'id' as const, value: id } }; +} + function requestCallOptions(options?: SandboxCallOptions | null): CallOptions | undefined { if (!options) return undefined; const { workspace: _workspace, ...callOptions } = options; @@ -800,7 +811,7 @@ export class SandboxClient { async get(name: string, options?: SandboxCallOptions | null): Promise { try { const resp = await this.grpc.getSandbox( - { name, workspaceScope: workspaceScope(options) }, + { sandboxRef: sandboxReferenceByName(name, options) }, requestCallOptions(options), ); return sandboxRef(resp.sandbox); @@ -825,7 +836,7 @@ export class SandboxClient { async delete(name: string, options?: SandboxWorkspaceOptions | null): Promise { try { - const resp = await this.grpc.deleteSandbox({ name, workspaceScope: workspaceScope(options) }); + const resp = await this.grpc.deleteSandbox({ sandboxRef: sandboxReferenceByName(name, options) }); return resp.deleted; } catch (e) { throw fromConnect(e); @@ -901,7 +912,7 @@ export class SandboxClient { }); const stream = this.grpc.execSandbox( { - sandboxId: sandbox.id, + sandboxRef: sandboxReferenceById(sandbox.id), command, workdir: options?.workdir ?? '', environment: options?.environment ?? {}, @@ -984,7 +995,7 @@ export class SandboxClient { payload: { case: 'start', value: { - sandboxId, + sandboxRef: sandboxReferenceById(sandboxId), command, workdir: options?.workdir ?? '', environment: options?.environment ?? {}, @@ -1204,7 +1215,7 @@ export class SandboxClient { const input = new Pushable>(); input.onDrain = () => socket.resume(); try { - const session = await this.grpc.createSshSession({ sandboxId }, { signal }); + const session = await this.grpc.createSshSession({ sandboxRef: sandboxReferenceById(sandboxId) }, { signal }); // Defense-in-depth: the token feeds forwardTcp authorization, so hold it // to the same trust-boundary contract as createSshSession. A violation // tears down this one socket via the catch below. @@ -1214,7 +1225,7 @@ export class SandboxClient { payload: { case: 'init', value: { - sandboxId, + sandboxRef: sandboxReferenceById(sandboxId), serviceId: `service-forward:${name}:${targetHost}:${targetPort}`, target: { case: 'tcp', @@ -1277,7 +1288,7 @@ export class SandboxClient { async createSshSession(name: string, options?: SandboxWorkspaceOptions | null): Promise { try { const sandbox = await this.get(name, options); - const resp = await this.grpc.createSshSession({ sandboxId: sandbox.id }); + const resp = await this.grpc.createSshSession({ sandboxRef: sandboxReferenceById(sandbox.id) }); // Reject any response outside the proto trust-boundary contract before // handing these values to the caller (they feed OpenSSH ProxyCommand). validateSshResponse(resp, sandbox.id); @@ -1311,10 +1322,9 @@ export class SandboxClient { ): Promise { try { const resp = await this.grpc.attachSandboxProvider({ - sandboxName: name, + sandboxRef: sandboxReferenceByName(name, options), providerName: provider, expectedResourceVersion: versionPin(options?.expectedResourceVersion), - workspaceScope: workspaceScope(options), }); return { sandbox: sandboxRef(resp.sandbox), changed: resp.attached }; } catch (e) { @@ -1329,10 +1339,9 @@ export class SandboxClient { ): Promise { try { const resp = await this.grpc.detachSandboxProvider({ - sandboxName: name, + sandboxRef: sandboxReferenceByName(name, options), providerName: provider, expectedResourceVersion: versionPin(options?.expectedResourceVersion), - workspaceScope: workspaceScope(options), }); return { sandbox: sandboxRef(resp.sandbox), changed: resp.detached }; } catch (e) { @@ -1343,8 +1352,7 @@ export class SandboxClient { async listProviders(name: string, options?: SandboxWorkspaceOptions | null): Promise { try { const resp = await this.grpc.listSandboxProviders({ - sandboxName: name, - workspaceScope: workspaceScope(options), + sandboxRef: sandboxReferenceByName(name, options), }); return resp.providers.map((p) => providerRef(p)); } catch (e) { @@ -1355,7 +1363,10 @@ export class SandboxClient { async getConfig(name: string, options?: SandboxCallOptions | null): Promise { try { const sandbox = await this.get(name, options); - const resp = await this.grpc.getSandboxConfig({ sandboxId: sandbox.id }, requestCallOptions(options)); + const resp = await this.grpc.getSandboxConfig( + { sandboxRef: sandboxReferenceById(sandbox.id) }, + requestCallOptions(options), + ); return sandboxConfig(resp); } catch (e) { throw e instanceof SdkError ? e : fromConnect(e); @@ -1373,11 +1384,10 @@ export class SandboxClient { ): Promise { try { const resp = await this.grpc.updateConfig({ - name, + sandboxRef: sandboxReferenceByName(name, options), policy, global: false, expectedResourceVersion: versionPin(options?.expectedResourceVersion), - workspaceScope: workspaceScope(options), }); const result = updateConfigResult(resp); if (options?.wait) @@ -1398,11 +1408,10 @@ export class SandboxClient { ): Promise { try { const resp = await this.grpc.updateConfig({ - name, + sandboxRef: sandboxReferenceByName(name, options), settingKey: key, settingValue: value, global: false, - workspaceScope: workspaceScope(options), }); return updateConfigResult(resp); } catch (e) { diff --git a/skills/openshell-cli/SKILL.md b/skills/openshell-cli/SKILL.md index ed6449619..9bbda7d78 100644 --- a/skills/openshell-cli/SKILL.md +++ b/skills/openshell-cli/SKILL.md @@ -798,6 +798,9 @@ openshell service get my-app web openshell service delete my-app web ``` +Use `openshell service list --all-workspaces` for a Platform Admin view across +workspaces. A sandbox name and `--all-workspaces` are mutually exclusive. + Prefer loopback binds unless the user explicitly needs LAN-visible local access. --- diff --git a/tasks/scripts/generate_python_proto.py b/tasks/scripts/generate_python_proto.py index 76664eb41..cefd2c375 100644 --- a/tasks/scripts/generate_python_proto.py +++ b/tasks/scripts/generate_python_proto.py @@ -56,6 +56,12 @@ LINE_REWRITES = { "from . import datamodel_pb2 as datamodel__pb2", ), ], + "python/openshell/_proto/sandbox_pb2.py": [ + ( + r"^import datamodel_pb2 as datamodel__pb2$", + "from . import datamodel_pb2 as datamodel__pb2", + ), + ], "python/openshell/_proto/sandbox_pb2_grpc.py": [ ( r"^import sandbox_pb2 as sandbox__pb2$",