From b907685b3f4770be9d14e4d6577b65d6259ef8b2 Mon Sep 17 00:00:00 2001 From: Piotr Mlocek Date: Tue, 6 Oct 2026 08:50:57 -0700 Subject: [PATCH] feat(config): schedule configuration delivery without drops Replace the bounded delivery queue with a synchronous scheduler that never rejects admission. Every publication takes a sequence number and a key is rebuilt only while its last build predates the publication, so repeated publications coalesce. Fanouts are cursors over sorted recipients; direct and fanout builds share build slots with a direct reserve and a fanout floor, and a separate route lane handles owner lookups and peer hints. Provider instance changes and credential refreshes publish a targeted provider scope that fans out only to sandboxes attached to that provider, filtered by a monotonic attachment cache. Add the PeerConfigProviderTarget hint scope and remove the unreleased queue_full field. Push-mode sessions get one latest-wins slot per component, drained by an outbound stream that sends control frames first. Sessions register with the scheduler only after setup succeeds and only while still current, and recipient epochs fence stale tickets. Signed-off-by: Piotr Mlocek --- .../openshell-server/src/config_delivery.rs | 1417 ---------------- .../src/config_delivery/mod.rs | 1431 +++++++++++++++++ .../src/config_delivery/scheduler.rs | 1342 ++++++++++++++++ .../src/config_delivery/session_slots.rs | 178 ++ crates/openshell-server/src/grpc/mod.rs | 2 +- crates/openshell-server/src/grpc/policy.rs | 15 +- crates/openshell-server/src/grpc/provider.rs | 33 +- crates/openshell-server/src/lib.rs | 10 +- .../openshell-server/src/provider_refresh.rs | 41 +- crates/openshell-server/src/storage_proto.rs | 4 +- .../src/supervisor_session.rs | 297 ++-- proto/openshell.proto | 9 +- sdk/go/proto/openshellv1/openshell.pb.go | 1341 +++++++-------- 13 files changed, 3890 insertions(+), 2230 deletions(-) delete mode 100644 crates/openshell-server/src/config_delivery.rs create mode 100644 crates/openshell-server/src/config_delivery/mod.rs create mode 100644 crates/openshell-server/src/config_delivery/scheduler.rs create mode 100644 crates/openshell-server/src/config_delivery/session_slots.rs diff --git a/crates/openshell-server/src/config_delivery.rs b/crates/openshell-server/src/config_delivery.rs deleted file mode 100644 index a28878213..000000000 --- a/crates/openshell-server/src/config_delivery.rs +++ /dev/null @@ -1,1417 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -//! Build and route complete supervisor configuration snapshots. - -use std::collections::HashMap; -use std::collections::hash_map::Entry; -use std::fmt; -use std::sync::{Arc, Mutex}; -use std::time::Duration; - -use futures::StreamExt; -use metrics::counter; -use openshell_core::config::ConfigDeliveryMode; -use openshell_core::proto::{ - ConfigBootstrap, PeerConfigSandboxTarget, PeerConfigUpdateHintRequest, - PeerConfigUpdateHintResponse, ProviderEnvironmentSnapshot, Sandbox, SandboxConfigSnapshot, - peer_config_update_hint_request, -}; -use tokio::sync::{OwnedSemaphorePermit, Semaphore}; -use tonic::{Code, Request, Response, Status}; -use tracing::warn; - -use crate::ServerState; -use crate::auth::principal::Principal; -use crate::grpc::policy::{build_provider_environment_snapshot, build_sandbox_config_snapshot}; -use crate::persistence::ObjectWorkspace; -use crate::supervisor_owner::{OWNER_TTL, SupervisorOwnerIndex}; -use crate::supervisor_session::SupervisorSessionRegistry; - -/// Leaves headroom below tonic's default 4 MiB decode limit for framing and -/// future envelope fields. -pub const MAX_SUPERVISOR_CONFIG_MESSAGE_BYTES: usize = 3 * 1024 * 1024; -const CONFIG_SNAPSHOT_BUILD_TIMEOUT: Duration = Duration::from_secs(45); -// Stage 1 bootstrap is optional. Keep credential backend stalls well below -// the 15-second relay session-wait budget while polling remains authoritative. -const CONFIG_BOOTSTRAP_BUILD_TIMEOUT: Duration = Duration::from_secs(1); -const MAX_ACTIVE_FANOUT_WORKERS: usize = 64; -/// Concurrent snapshot builds allowed per pooled database connection. Builds -/// are short bursts of small queries, so a little oversubscription keeps the -/// pool busy without stacking every waiter on the acquire timeout. -const SNAPSHOT_BUILDS_PER_DB_CONNECTION: usize = 2; -const MIN_CONCURRENT_SNAPSHOT_BUILDS: usize = 4; - -/// One complete configuration component awaiting delivery to a supervisor. -#[derive(Clone)] -pub enum SupervisorConfigMessage { - SandboxConfig(Box), - ProviderEnvironment(ProviderEnvironmentSnapshot), -} - -impl SupervisorConfigMessage { - pub(crate) fn component_name(&self) -> &'static str { - match self { - Self::SandboxConfig(_) => "sandbox_config", - Self::ProviderEnvironment(_) => "provider_environment", - } - } -} - -impl fmt::Debug for SupervisorConfigMessage { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str(match self { - Self::SandboxConfig(_) => "SandboxConfig()", - Self::ProviderEnvironment(_) => "ProviderEnvironment()", - }) - } -} - -/// Result of routing one configuration snapshot toward a supervisor session. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DeliveryDisposition { - Enqueued, - NoActiveSession, - UnsupportedSession, - QueueFull, - SessionClosed, - PayloadTooLarge, -} - -/// Transport boundary for configuration delivery. -#[tonic::async_trait] -pub trait SupervisorConfigRouter: fmt::Debug + Send + Sync { - async fn deliver( - &self, - sandbox_id: &str, - message: SupervisorConfigMessage, - ) -> DeliveryDisposition; - - async fn routable_sandbox_ids(&self) -> Vec; -} - -#[derive(Debug)] -pub struct LocalSupervisorConfigRouter { - sessions: Arc, -} - -impl LocalSupervisorConfigRouter { - #[must_use] - pub fn new(sessions: Arc) -> Self { - Self { sessions } - } -} - -#[tonic::async_trait] -impl SupervisorConfigRouter for LocalSupervisorConfigRouter { - async fn deliver( - &self, - sandbox_id: &str, - message: SupervisorConfigMessage, - ) -> DeliveryDisposition { - self.sessions.deliver_config(sandbox_id, message) - } - - async fn routable_sandbox_ids(&self) -> Vec { - self.sessions.connected_sandbox_ids() - } -} - -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub struct ConfigComponents { - pub sandbox_config: bool, - pub provider_environment: bool, -} - -impl ConfigComponents { - pub const ALL: Self = Self { - sandbox_config: true, - provider_environment: true, - }; - - pub const SANDBOX_AND_PROVIDER: Self = Self { - sandbox_config: true, - provider_environment: true, - }; - - pub const SANDBOX_CONFIG: Self = Self { - sandbox_config: true, - provider_environment: false, - }; - - fn selected(self) -> impl Iterator { - [ - (self.sandbox_config, ConfigComponentKind::SandboxConfig), - ( - self.provider_environment, - ConfigComponentKind::ProviderEnvironment, - ), - ] - .into_iter() - .filter_map(|(selected, component)| selected.then_some(component)) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum ConfigComponentKind { - SandboxConfig, - ProviderEnvironment, -} - -impl ConfigComponentKind { - fn name(self) -> &'static str { - match self { - Self::SandboxConfig => "sandbox_config", - Self::ProviderEnvironment => "provider_environment", - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct DeliveryKey { - sandbox_id: String, - component: ConfigComponentKind, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum FanoutScope { - Workspace(String), - AllConnected, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct FanoutKey { - scope: FanoutScope, - component: ConfigComponentKind, - route_remote: bool, -} - -/// Coalesces publications and bounds workers per sandbox and component. -/// -/// The map entry is also the worker lease. Its boolean is set when another -/// mutation arrives during a build or route operation. The worker then rebuilds -/// the current full snapshot once, regardless of how many mutations arrived. -/// -/// Each pending map entry owns a delivery permit for its worker. Direct -/// publications fail fast when all permits are held. Fanout workers wait for a -/// permit before admitting the next recipient, which bounds both spawned tasks -/// and pending keys while still walking the full recipient list. -#[derive(Debug)] -pub struct ConfigDeliveryQueue { - pending: Mutex>, - fanout_pending: Mutex>, - delivery_permits: Arc, - build_permits: Semaphore, -} - -impl Default for ConfigDeliveryQueue { - fn default() -> Self { - Self::new(MIN_CONCURRENT_SNAPSHOT_BUILDS) - } -} - -impl ConfigDeliveryQueue { - #[must_use] - pub fn new(max_concurrent_builds: usize) -> Self { - let max_concurrent_builds = max_concurrent_builds.max(1); - Self { - pending: Mutex::default(), - fanout_pending: Mutex::default(), - delivery_permits: Arc::new(Semaphore::new(max_concurrent_builds)), - build_permits: Semaphore::new(max_concurrent_builds), - } - } - - /// Size the build bound from the persistence pool that every build reads. - #[must_use] - pub fn for_db_connections(max_connections: u32) -> Self { - let max_connections = usize::try_from(max_connections).unwrap_or(usize::MAX); - Self::new( - max_connections - .saturating_mul(SNAPSHOT_BUILDS_PER_DB_CONNECTION) - .max(MIN_CONCURRENT_SNAPSHOT_BUILDS), - ) - } - - #[cfg(test)] - fn max_concurrent_builds(&self) -> usize { - self.build_permits.available_permits() - } - - /// Run one snapshot build under the concurrency bound. The deadline starts - /// only once a permit is held so queued builds do not spend their budget - /// waiting. - async fn run_bounded_build( - &self, - build: impl Future, - ) -> Result { - let _permit = self - .build_permits - .acquire() - .await - .expect("snapshot build semaphore is never closed"); - tokio::time::timeout(CONFIG_SNAPSHOT_BUILD_TIMEOUT, build).await - } - - fn enqueue(&self, key: DeliveryKey) -> DeliveryEnqueue { - let mut pending = self.pending.lock().unwrap(); - match pending.entry(key) { - Entry::Occupied(mut entry) => { - *entry.get_mut() = true; - DeliveryEnqueue::Coalesced - } - Entry::Vacant(entry) => { - let Ok(permit) = Arc::clone(&self.delivery_permits).try_acquire_owned() else { - return DeliveryEnqueue::Full; - }; - entry.insert(true); - DeliveryEnqueue::StartWorker(permit) - } - } - } - - async fn enqueue_from_fanout(&self, key: DeliveryKey) -> DeliveryEnqueue { - { - let mut pending = self.pending.lock().unwrap(); - if let Entry::Occupied(mut entry) = pending.entry(key.clone()) { - *entry.get_mut() = true; - return DeliveryEnqueue::Coalesced; - } - } - - let permit = Arc::clone(&self.delivery_permits) - .acquire_owned() - .await - .expect("delivery worker semaphore is never closed"); - let mut pending = self.pending.lock().unwrap(); - match pending.entry(key) { - Entry::Occupied(mut entry) => { - *entry.get_mut() = true; - DeliveryEnqueue::Coalesced - } - Entry::Vacant(entry) => { - entry.insert(true); - DeliveryEnqueue::StartWorker(permit) - } - } - } - - fn take(&self, key: &DeliveryKey) { - let mut pending = self.pending.lock().unwrap(); - if let Some(changed) = pending.get_mut(key) { - *changed = false; - } - } - - fn finish_pass(&self, key: &DeliveryKey) -> bool { - let mut pending = self.pending.lock().unwrap(); - if pending.get(key).is_some_and(|changed| !changed) { - pending.remove(key); - false - } else { - pending.contains_key(key) - } - } - - fn enqueue_fanout(&self, key: FanoutKey) -> FanoutEnqueue { - let mut pending = self.fanout_pending.lock().unwrap(); - if let Some(changed) = pending.get_mut(&key) { - *changed = true; - return FanoutEnqueue::Coalesced; - } - if pending.len() >= MAX_ACTIVE_FANOUT_WORKERS { - FanoutEnqueue::Full - } else { - pending.insert(key, true); - FanoutEnqueue::StartWorker - } - } - - fn take_fanout(&self, key: &FanoutKey) { - let mut pending = self.fanout_pending.lock().unwrap(); - if let Some(changed) = pending.get_mut(key) { - *changed = false; - } - } - - fn finish_fanout_pass(&self, key: &FanoutKey) -> bool { - let mut pending = self.fanout_pending.lock().unwrap(); - if pending.get(key).is_some_and(|changed| !changed) { - pending.remove(key); - false - } else { - pending.contains_key(key) - } - } -} - -#[derive(Debug)] -enum DeliveryEnqueue { - StartWorker(OwnedSemaphorePermit), - Coalesced, - Full, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum FanoutEnqueue { - StartWorker, - Coalesced, - Full, -} - -pub async fn build_config_bootstrap( - state: &Arc, - sandbox: &Sandbox, -) -> Result { - tokio::time::timeout( - CONFIG_BOOTSTRAP_BUILD_TIMEOUT, - build_consistent_config_bootstrap(state, sandbox), - ) - .await - .map_err(|_| Status::deadline_exceeded("supervisor configuration bootstrap timed out"))? -} - -async fn build_consistent_config_bootstrap( - state: &Arc, - sandbox: &Sandbox, -) -> Result { - const MAX_BUILD_ATTEMPTS: usize = 3; - for _ in 0..MAX_BUILD_ATTEMPTS { - // Components are independent projections. The provider revision is a - // fence for the only overlapping input between sandbox configuration - // and provider environment state. - let (sandbox_config, provider_environment) = tokio::join!( - build_sandbox_config_snapshot(state, sandbox), - build_provider_environment_snapshot(state, sandbox, true), - ); - let bootstrap = ConfigBootstrap { - sandbox_config: Some(sandbox_config?), - provider_environment: Some(provider_environment?), - }; - if bootstrap_revisions_match(&bootstrap) { - return Ok(bootstrap); - } - counter!("openshell_supervisor_config_bootstrap_revision_mismatches_total").increment(1); - } - Err(Status::aborted( - "configuration changed while building supervisor bootstrap", - )) -} - -fn bootstrap_revisions_match(bootstrap: &ConfigBootstrap) -> bool { - bootstrap - .sandbox_config - .as_ref() - .zip(bootstrap.provider_environment.as_ref()) - .is_some_and(|(sandbox, provider)| { - sandbox.provider_env_revision == provider.provider_env_revision - }) -} - -pub fn publish_sandbox_components( - state: &Arc, - sandbox_id: &str, - components: ConfigComponents, -) { - if state.config.config_delivery_mode != ConfigDeliveryMode::Push { - return; - } - enqueue_sandbox(state, sandbox_id, components); -} - -fn enqueue_sandbox( - state: &Arc, - sandbox_id: &str, - components: ConfigComponents, -) -> bool { - let mut accepted = true; - for component in components.selected() { - let key = DeliveryKey { - sandbox_id: sandbox_id.to_string(), - component, - }; - match state.config_delivery_queue.enqueue(key.clone()) { - DeliveryEnqueue::StartWorker(permit) => { - spawn_delivery_worker(state, key, permit); - } - DeliveryEnqueue::Coalesced => {} - DeliveryEnqueue::Full => { - accepted = false; - record_delivery_worker_full(sandbox_id, component.name()); - } - } - } - accepted -} - -fn spawn_delivery_worker(state: &Arc, key: DeliveryKey, permit: OwnedSemaphorePermit) { - let state = Arc::clone(state); - tokio::spawn(async move { - let _permit = permit; - loop { - state.config_delivery_queue.take(&key); - publish_sandbox_component_now(&state, &key).await; - if !state.config_delivery_queue.finish_pass(&key) { - break; - } - } - }); -} - -async fn enqueue_sandbox_from_fanout( - state: &Arc, - sandbox_id: &str, - component: ConfigComponentKind, -) { - let key = DeliveryKey { - sandbox_id: sandbox_id.to_string(), - component, - }; - match state - .config_delivery_queue - .enqueue_from_fanout(key.clone()) - .await - { - DeliveryEnqueue::StartWorker(permit) => spawn_delivery_worker(state, key, permit), - DeliveryEnqueue::Coalesced => {} - DeliveryEnqueue::Full => unreachable!("fanout waits for delivery worker capacity"), - } -} - -async fn publish_sandbox_component_now(state: &Arc, key: &DeliveryKey) { - if route_remote_sandbox_component(state, key).await { - return; - } - if !is_current_local_owner(state, &key.sandbox_id).await { - return; - } - if !state - .supervisor_sessions - .is_config_push_capable(&key.sandbox_id) - { - return; - } - let component = key.component.name(); - let build = async { - let sandbox = state - .store - .get_message::(&key.sandbox_id) - .await - .map_err(|error| Status::internal(format!("fetch sandbox failed: {error}")))?; - let Some(sandbox) = sandbox else { - return Ok(None); - }; - match key.component { - ConfigComponentKind::SandboxConfig => build_sandbox_config_snapshot(state, &sandbox) - .await - .map(|snapshot| SupervisorConfigMessage::SandboxConfig(Box::new(snapshot))), - ConfigComponentKind::ProviderEnvironment => { - build_provider_environment_snapshot(state, &sandbox, true) - .await - .map(SupervisorConfigMessage::ProviderEnvironment) - } - } - .map(Some) - }; - match state.config_delivery_queue.run_bounded_build(build).await { - Ok(Ok(None)) => {} - Ok(Ok(Some(message))) => { - if !is_current_local_owner(state, &key.sandbox_id).await { - return; - } - let disposition = state - .supervisor_config_router() - .deliver(&key.sandbox_id, message) - .await; - record_delivery(component, disposition); - } - Ok(Err(error)) => { - record_build_failure(&key.sandbox_id, component, error.code()); - } - Err(_) => { - record_build_failure(&key.sandbox_id, component, Code::DeadlineExceeded); - } - } -} - -async fn is_current_local_owner(state: &Arc, sandbox_id: &str) -> bool { - let owners = SupervisorOwnerIndex::new(Arc::clone(&state.store), OWNER_TTL); - match owners.read(sandbox_id).await { - Ok(Some(owner)) => { - owner.is_fresh(OWNER_TTL) - && owner.owner_replica_id == state.replica_id - && state - .supervisor_sessions - .is_current_session(sandbox_id, &owner.session_id) - } - Ok(None) => false, - Err(error) => { - warn!(sandbox_id, error = %error, "configuration owner lookup failed"); - false - } - } -} - -/// Return true when the current owner is remote or the session is gone. -/// The owner builds its own snapshot after receiving this secret-free hint. -async fn route_remote_sandbox_component(state: &Arc, key: &DeliveryKey) -> bool { - let owners = SupervisorOwnerIndex::new(Arc::clone(&state.store), OWNER_TTL); - let mut final_outcome = "stale_owner"; - for attempt in 0..2 { - let owner = match owners.read(&key.sandbox_id).await { - Ok(Some(owner)) if owner.is_fresh(OWNER_TTL) => owner, - Ok(_) => return true, - Err(error) => { - warn!(sandbox_id = %key.sandbox_id, error = %error, "configuration owner lookup failed"); - return true; - } - }; - if owner.owner_replica_id == state.replica_id { - return !state - .supervisor_sessions - .is_current_session(&key.sandbox_id, &owner.session_id); - } - if !owner.owner_peer_endpoint.starts_with("http://") - && !owner.owner_peer_endpoint.starts_with("https://") - { - warn!(sandbox_id = %key.sandbox_id, owner = %owner.owner_replica_id, "configuration owner has no reachable peer endpoint"); - return true; - } - let request = PeerConfigUpdateHintRequest { - scope: Some(peer_config_update_hint_request::Scope::Sandbox( - PeerConfigSandboxTarget { - sandbox_id: key.sandbox_id.clone(), - session_id: owner.session_id, - }, - )), - sandbox_config: key.component == ConfigComponentKind::SandboxConfig, - provider_environment: key.component == ConfigComponentKind::ProviderEnvironment, - }; - let result = tokio::time::timeout( - Duration::from_secs(5), - crate::supervisor_session::forward_config_hint_to_peer( - state, - &owner.owner_peer_endpoint, - request, - ), - ) - .await; - match result { - Ok(Ok(response)) if !response.stale_owner && !response.queue_full => { - counter!("openshell_supervisor_config_peer_hints_total", "outcome" => "accepted") - .increment(1); - return true; - } - Ok(Ok(response)) if response.queue_full => { - counter!("openshell_supervisor_config_peer_hints_total", "outcome" => "queue_full") - .increment(1); - return true; - } - Ok(Err(error)) if error.code() == Code::Unimplemented => { - counter!("openshell_supervisor_config_peer_hints_total", "outcome" => "unsupported_peer") - .increment(1); - return true; - } - Ok(Err(error)) => { - final_outcome = "peer_error"; - warn!(sandbox_id = %key.sandbox_id, owner = %owner.owner_replica_id, attempt, code = ?error.code(), "configuration peer hint failed"); - } - Err(_) => { - final_outcome = "timeout"; - warn!(sandbox_id = %key.sandbox_id, owner = %owner.owner_replica_id, attempt, "configuration peer hint timed out"); - } - Ok(Ok(_)) => final_outcome = "stale_owner", - } - } - counter!("openshell_supervisor_config_peer_hints_total", "outcome" => final_outcome) - .increment(1); - true -} - -pub fn publish_workspace_components( - state: &Arc, - workspace: &str, - components: ConfigComponents, -) { - if state.config.config_delivery_mode != ConfigDeliveryMode::Push { - return; - } - enqueue_fanout( - state, - FanoutScope::Workspace(workspace.to_string()), - components, - true, - ); -} - -pub fn publish_all_connected(state: &Arc, components: ConfigComponents) { - if state.config.config_delivery_mode != ConfigDeliveryMode::Push { - return; - } - enqueue_fanout(state, FanoutScope::AllConnected, components, true); -} - -fn enqueue_fanout( - state: &Arc, - scope: FanoutScope, - components: ConfigComponents, - route_remote: bool, -) -> bool { - let mut accepted = true; - for component in components.selected() { - let key = FanoutKey { - scope: scope.clone(), - component, - route_remote, - }; - match state.config_delivery_queue.enqueue_fanout(key.clone()) { - FanoutEnqueue::StartWorker => { - let state = Arc::clone(state); - tokio::spawn(async move { - loop { - state.config_delivery_queue.take_fanout(&key); - publish_fanout_now(&state, &key).await; - if !state.config_delivery_queue.finish_fanout_pass(&key) { - break; - } - } - }); - } - FanoutEnqueue::Coalesced => {} - FanoutEnqueue::Full => { - accepted = false; - counter!("openshell_supervisor_config_fanout_total", "outcome" => "queue_full") - .increment(1); - warn!( - component = component.name(), - "supervisor configuration fanout queue is full" - ); - } - } - } - accepted -} - -async fn publish_fanout_now(state: &Arc, key: &FanoutKey) { - if key.route_remote { - notify_remote_fanout(state, key).await; - } - let sandbox_ids = state - .supervisor_config_router() - .routable_sandbox_ids() - .await; - for sandbox_id in sandbox_ids { - if let FanoutScope::Workspace(workspace) = &key.scope { - let sandbox = match state.store.get_message::(&sandbox_id).await { - Ok(Some(sandbox)) => sandbox, - Ok(None) => continue, - Err(_) => { - record_build_failure(&sandbox_id, "sandbox", Code::Internal); - continue; - } - }; - if sandbox.object_workspace() != workspace { - continue; - } - } - enqueue_sandbox_from_fanout(state, &sandbox_id, key.component).await; - } -} - -async fn notify_remote_fanout(state: &Arc, key: &FanoutKey) { - let owners = SupervisorOwnerIndex::new(Arc::clone(&state.store), OWNER_TTL); - let endpoints = match owners.list_fresh_peer_endpoints(&state.replica_id).await { - Ok(endpoints) => endpoints, - Err(error) => { - warn!(error = %error, "configuration peer fanout owner listing failed"); - return; - } - }; - futures::stream::iter(endpoints) - .for_each_concurrent(8, |endpoint| async move { - let scope = match &key.scope { - FanoutScope::Workspace(workspace) => { - peer_config_update_hint_request::Scope::Workspace(workspace.clone()) - } - FanoutScope::AllConnected => { - peer_config_update_hint_request::Scope::AllConnected(true) - } - }; - let request = PeerConfigUpdateHintRequest { - scope: Some(scope), - sandbox_config: key.component == ConfigComponentKind::SandboxConfig, - provider_environment: key.component == ConfigComponentKind::ProviderEnvironment, - }; - let result = tokio::time::timeout( - Duration::from_secs(5), - crate::supervisor_session::forward_config_hint_to_peer(state, &endpoint, request), - ) - .await; - match result { - Ok(Ok(response)) if !response.queue_full => { - counter!("openshell_supervisor_config_peer_hints_total", "outcome" => "accepted") - .increment(1); - } - Ok(Ok(_)) => { - counter!("openshell_supervisor_config_peer_hints_total", "outcome" => "queue_full") - .increment(1); - } - Ok(Err(error)) => { - warn!(endpoint = %endpoint, code = ?error.code(), "configuration peer fanout hint failed"); - } - Err(_) => { - warn!(endpoint = %endpoint, "configuration peer fanout hint timed out"); - } - } - }) - .await; -} - -pub async fn handle_peer_config_update_hint( - state: &Arc, - request: Request, -) -> Result, Status> { - if !matches!( - request.extensions().get::(), - Some(Principal::Peer(_)) - ) { - return Err(Status::permission_denied("gateway peer principal required")); - } - if state.config.config_delivery_mode != ConfigDeliveryMode::Push { - return Err(Status::failed_precondition( - "configuration push is disabled", - )); - } - let hint = request.into_inner(); - let components = ConfigComponents { - sandbox_config: hint.sandbox_config, - provider_environment: hint.provider_environment, - }; - if !components.sandbox_config && !components.provider_environment { - return Err(Status::invalid_argument( - "at least one configuration component is required", - )); - } - let mut response = PeerConfigUpdateHintResponse::default(); - match hint.scope { - Some(peer_config_update_hint_request::Scope::Sandbox(target)) => { - if target.sandbox_id.is_empty() || target.session_id.is_empty() { - return Err(Status::invalid_argument( - "sandbox and session IDs are required", - )); - } - let owners = SupervisorOwnerIndex::new(Arc::clone(&state.store), OWNER_TTL); - let owner = owners - .read(&target.sandbox_id) - .await - .map_err(|_| Status::unavailable("configuration owner lookup failed"))?; - response.stale_owner = owner.is_none_or(|owner| { - !owner.is_fresh(OWNER_TTL) - || owner.owner_replica_id != state.replica_id - || owner.session_id != target.session_id - || !state - .supervisor_sessions - .is_current_session(&target.sandbox_id, &target.session_id) - }); - if !response.stale_owner { - response.queue_full = !enqueue_sandbox(state, &target.sandbox_id, components); - } - } - Some(peer_config_update_hint_request::Scope::Workspace(workspace)) => { - if workspace.is_empty() { - return Err(Status::invalid_argument("workspace is required")); - } - response.queue_full = - !enqueue_fanout(state, FanoutScope::Workspace(workspace), components, false); - } - Some(peer_config_update_hint_request::Scope::AllConnected(true)) => { - response.queue_full = - !enqueue_fanout(state, FanoutScope::AllConnected, components, false); - } - _ => { - return Err(Status::invalid_argument( - "configuration hint scope is required", - )); - } - } - Ok(Response::new(response)) -} - -fn record_delivery_worker_full(sandbox_id: &str, component: &'static str) { - counter!( - "openshell_supervisor_config_delivery_workers_total", - "outcome" => "queue_full", - ) - .increment(1); - warn!( - sandbox_id, - component, "supervisor configuration delivery worker queue is full" - ); -} - -fn record_delivery(component: &'static str, disposition: DeliveryDisposition) { - let outcome = match disposition { - DeliveryDisposition::Enqueued => "enqueued", - DeliveryDisposition::NoActiveSession => "no_active_session", - DeliveryDisposition::UnsupportedSession => "unsupported_session", - DeliveryDisposition::QueueFull => "queue_full", - DeliveryDisposition::SessionClosed => "session_closed", - DeliveryDisposition::PayloadTooLarge => "payload_too_large", - }; - counter!( - "openshell_supervisor_config_deliveries_total", - "component" => component, - "outcome" => outcome, - ) - .increment(1); -} - -fn record_build_failure(sandbox_id: &str, component: &'static str, error_code: Code) { - counter!( - "openshell_supervisor_config_snapshot_failures_total", - "component" => component, - ) - .increment(1); - warn!( - sandbox_id = %sandbox_id, - component, - ?error_code, - "failed to build supervisor configuration snapshot" - ); -} - -#[cfg(test)] -mod tests { - use std::sync::atomic::{AtomicUsize, Ordering}; - - use super::*; - use crate::grpc::test_support::{connect_supervisor_stream, test_server_state}; - use openshell_core::proto::{GatewayMessage, ObjectMeta, SandboxSpec, gateway_message}; - - fn peer_hint(sandbox_id: &str, session_id: &str) -> Request { - let mut request = Request::new(PeerConfigUpdateHintRequest { - scope: Some(peer_config_update_hint_request::Scope::Sandbox( - PeerConfigSandboxTarget { - sandbox_id: sandbox_id.into(), - session_id: session_id.into(), - }, - )), - sandbox_config: true, - provider_environment: false, - }); - request - .extensions_mut() - .insert(Principal::Peer(crate::auth::principal::PeerPrincipal { - replica_id: "other-replica".into(), - pod_uid: "peer-pod".into(), - })); - request - } - - #[tokio::test] - async fn peer_hint_requires_push_and_current_session() { - let mut state = test_server_state().await; - let error = handle_peer_config_update_hint(&state, peer_hint("sandbox", "old-session")) - .await - .unwrap_err(); - assert_eq!(error.code(), Code::FailedPrecondition); - - Arc::get_mut(&mut state) - .unwrap() - .config - .config_delivery_mode = ConfigDeliveryMode::Push; - let response = handle_peer_config_update_hint(&state, peer_hint("sandbox", "old-session")) - .await - .unwrap() - .into_inner(); - assert!(response.stale_owner); - assert!(!response.queue_full); - - let error = handle_peer_config_update_hint( - &state, - Request::new(PeerConfigUpdateHintRequest::default()), - ) - .await - .unwrap_err(); - assert_eq!(error.code(), Code::PermissionDenied); - } - - #[tokio::test] - async fn peer_hint_rebuilds_snapshot_on_the_session_owner() { - let mut state = test_server_state().await; - Arc::get_mut(&mut state) - .unwrap() - .config - .config_delivery_mode = ConfigDeliveryMode::Push; - state - .store - .put_message(&Sandbox { - metadata: Some(ObjectMeta { - id: "owned-sandbox".into(), - name: "owned-sandbox".into(), - workspace: "default".into(), - ..Default::default() - }), - spec: Some(SandboxSpec::default()), - ..Default::default() - }) - .await - .unwrap(); - - let mut harness = connect_supervisor_stream( - &state, - "owned-sandbox", - openshell_core::proto::SUPERVISOR_PROTOCOL_REVISION, - ) - .await - .unwrap(); - let accepted = harness.inbound.message().await.unwrap().unwrap(); - let Some(gateway_message::Payload::SessionAccepted(accepted)) = accepted.payload else { - panic!("expected SessionAccepted"); - }; - let response = handle_peer_config_update_hint( - &state, - peer_hint("owned-sandbox", &accepted.session_id), - ) - .await - .unwrap() - .into_inner(); - assert!(!response.stale_owner && !response.queue_full); - let update = tokio::time::timeout(Duration::from_secs(5), harness.inbound.message()) - .await - .unwrap() - .unwrap() - .unwrap(); - assert!(matches!( - update.payload, - Some(gateway_message::Payload::ConfigUpdate(_)) - )); - } - - fn key(sandbox_id: &str, component: ConfigComponentKind) -> DeliveryKey { - DeliveryKey { - sandbox_id: sandbox_id.to_string(), - component, - } - } - - #[test] - fn queue_coalesces_repeated_component_changes_while_worker_is_active() { - let queue = ConfigDeliveryQueue::default(); - let key = key("sb-1", ConfigComponentKind::SandboxConfig); - let DeliveryEnqueue::StartWorker(permit) = queue.enqueue(key.clone()) else { - panic!("first publication must start a worker"); - }; - queue.take(&key); - assert!(matches!( - queue.enqueue(key.clone()), - DeliveryEnqueue::Coalesced - )); - assert!(matches!( - queue.enqueue(key.clone()), - DeliveryEnqueue::Coalesced - )); - assert!(queue.finish_pass(&key)); - queue.take(&key); - assert!(!queue.finish_pass(&key)); - drop(permit); - } - - #[tokio::test] - async fn direct_and_fanout_updates_share_one_component_worker() { - let queue = ConfigDeliveryQueue::default(); - let key = key("sb-1", ConfigComponentKind::SandboxConfig); - let DeliveryEnqueue::StartWorker(permit) = queue.enqueue(key.clone()) else { - panic!("direct update must start a worker"); - }; - queue.take(&key); - assert!(matches!( - queue.enqueue_from_fanout(key.clone()).await, - DeliveryEnqueue::Coalesced - )); - assert!(queue.finish_pass(&key)); - queue.take(&key); - assert!(!queue.finish_pass(&key)); - drop(permit); - } - - #[test] - fn build_bound_is_sized_from_the_database_pool() { - assert_eq!( - ConfigDeliveryQueue::for_db_connections(10).max_concurrent_builds(), - 20 - ); - assert_eq!( - ConfigDeliveryQueue::for_db_connections(1).max_concurrent_builds(), - MIN_CONCURRENT_SNAPSHOT_BUILDS - ); - assert_eq!(ConfigDeliveryQueue::new(0).max_concurrent_builds(), 1); - } - - #[tokio::test(start_paused = true)] - async fn bounded_builds_never_exceed_the_permit_count() { - const PERMITS: usize = 4; - const BUILDS: usize = 40; - let queue = Arc::new(ConfigDeliveryQueue::new(PERMITS)); - let active = Arc::new(AtomicUsize::new(0)); - let peak = Arc::new(AtomicUsize::new(0)); - - let workers = (0..BUILDS) - .map(|_| { - let queue = Arc::clone(&queue); - let active = Arc::clone(&active); - let peak = Arc::clone(&peak); - tokio::spawn(async move { - queue - .run_bounded_build(async { - let now = active.fetch_add(1, Ordering::SeqCst) + 1; - peak.fetch_max(now, Ordering::SeqCst); - tokio::time::sleep(Duration::from_millis(50)).await; - active.fetch_sub(1, Ordering::SeqCst); - }) - .await - .expect("build must not time out"); - }) - }) - .collect::>(); - for worker in workers { - worker.await.unwrap(); - } - - assert_eq!(peak.load(Ordering::SeqCst), PERMITS); - assert_eq!(active.load(Ordering::SeqCst), 0); - assert_eq!(queue.max_concurrent_builds(), PERMITS); - } - - #[tokio::test(start_paused = true)] - async fn build_deadline_starts_after_a_permit_is_held() { - let queue = Arc::new(ConfigDeliveryQueue::new(1)); - let almost_deadline = CONFIG_SNAPSHOT_BUILD_TIMEOUT - .checked_sub(Duration::from_secs(1)) - .unwrap(); - let first = { - let queue = Arc::clone(&queue); - tokio::spawn(async move { - queue - .run_bounded_build(tokio::time::sleep(almost_deadline)) - .await - }) - }; - tokio::task::yield_now().await; - let second = queue.run_bounded_build(tokio::time::sleep(almost_deadline)); - - let (first, second) = tokio::join!(first, second); - assert!(first.unwrap().is_ok()); - assert!( - second.is_ok(), - "waiting for a permit must not consume the build deadline" - ); - - assert!( - queue - .run_bounded_build(tokio::time::sleep( - CONFIG_SNAPSHOT_BUILD_TIMEOUT + Duration::from_secs(1), - )) - .await - .is_err() - ); - } - - #[test] - fn queue_runs_components_and_sandboxes_independently() { - let queue = ConfigDeliveryQueue::new(3); - assert!(matches!( - queue.enqueue(key("sb-1", ConfigComponentKind::SandboxConfig)), - DeliveryEnqueue::StartWorker(_) - )); - assert!(matches!( - queue.enqueue(key("sb-1", ConfigComponentKind::ProviderEnvironment)), - DeliveryEnqueue::StartWorker(_) - )); - assert!(matches!( - queue.enqueue(key("sb-2", ConfigComponentKind::SandboxConfig)), - DeliveryEnqueue::StartWorker(_) - )); - } - - #[tokio::test] - async fn fleet_fanout_waits_without_creating_unbounded_delivery_workers() { - const ROUTED_SANDBOXES: usize = 10_000; - let queue = Arc::new(ConfigDeliveryQueue::new(1)); - let first = key("sandbox-0", ConfigComponentKind::SandboxConfig); - let DeliveryEnqueue::StartWorker(_blocked_worker) = queue.enqueue(first) else { - panic!("first publication must start a worker"); - }; - - let sandbox_ids = (1..ROUTED_SANDBOXES) - .map(|index| format!("sandbox-{index}")) - .collect::>(); - let fanout = async { - for sandbox_id in sandbox_ids { - for component in ConfigComponents::ALL.selected() { - let _ = queue.enqueue_from_fanout(key(&sandbox_id, component)).await; - } - } - }; - tokio::pin!(fanout); - tokio::select! { - () = &mut fanout => panic!("fanout must wait for worker capacity"), - () = tokio::task::yield_now() => {} - } - - assert_eq!(queue.pending.lock().unwrap().len(), 1); - } - - #[test] - fn fanout_queue_coalesces_and_bounds_distinct_scopes() { - let queue = ConfigDeliveryQueue::default(); - let first = FanoutKey { - scope: FanoutScope::Workspace("workspace-0".into()), - component: ConfigComponentKind::SandboxConfig, - route_remote: true, - }; - assert_eq!( - queue.enqueue_fanout(first.clone()), - FanoutEnqueue::StartWorker - ); - queue.take_fanout(&first); - assert_eq!( - queue.enqueue_fanout(first.clone()), - FanoutEnqueue::Coalesced - ); - assert!(queue.finish_fanout_pass(&first)); - - for index in 1..MAX_ACTIVE_FANOUT_WORKERS { - assert_eq!( - queue.enqueue_fanout(FanoutKey { - scope: FanoutScope::Workspace(format!("workspace-{index}")), - component: ConfigComponentKind::SandboxConfig, - route_remote: true, - }), - FanoutEnqueue::StartWorker - ); - } - assert_eq!( - queue.enqueue_fanout(FanoutKey { - scope: FanoutScope::Workspace("overflow".into()), - component: ConfigComponentKind::SandboxConfig, - route_remote: true, - }), - FanoutEnqueue::Full - ); - } - - #[test] - fn configuration_message_debug_output_redacts_payloads() { - let message = SupervisorConfigMessage::ProviderEnvironment(ProviderEnvironmentSnapshot { - values: vec![openshell_core::proto::ProviderEnvironmentValue { - name: "TOKEN".into(), - value: "secret-marker".into(), - ..Default::default() - }], - ..Default::default() - }); - assert!(!format!("{message:?}").contains("secret-marker")); - } - - #[tokio::test] - async fn session_acceptance_precedes_live_configuration_updates() { - let mut state = test_server_state().await; - Arc::get_mut(&mut state) - .unwrap() - .config - .config_delivery_mode = ConfigDeliveryMode::Push; - state - .store - .put_message(&Sandbox { - metadata: Some(ObjectMeta { - id: "sandbox".into(), - name: "sandbox".into(), - workspace: "default".into(), - ..Default::default() - }), - spec: Some(SandboxSpec::default()), - ..Default::default() - }) - .await - .unwrap(); - - let mut harness = connect_supervisor_stream( - &state, - "sandbox", - openshell_core::proto::SUPERVISOR_PROTOCOL_REVISION, - ) - .await - .unwrap(); - - let first = tokio::time::timeout(Duration::from_secs(5), harness.inbound.message()) - .await - .unwrap() - .unwrap() - .unwrap(); - assert!(matches!( - first.payload, - Some(gateway_message::Payload::SessionAccepted(_)) - )); - - publish_sandbox_components(&state, "sandbox", ConfigComponents::SANDBOX_CONFIG); - let update = tokio::time::timeout(Duration::from_secs(5), harness.inbound.message()) - .await - .unwrap() - .unwrap() - .unwrap(); - assert!(matches!( - update, - GatewayMessage { - payload: Some(gateway_message::Payload::ConfigUpdate(_)) - } - )); - } - - #[tokio::test] - async fn legacy_supervisor_keeps_polling_when_gateway_shadow_push_is_enabled() { - let mut state = test_server_state().await; - Arc::get_mut(&mut state) - .unwrap() - .config - .config_delivery_mode = ConfigDeliveryMode::Push; - state - .store - .put_message(&Sandbox { - metadata: Some(ObjectMeta { - id: "legacy-sandbox".into(), - name: "legacy-sandbox".into(), - workspace: "default".into(), - ..Default::default() - }), - spec: Some(SandboxSpec::default()), - ..Default::default() - }) - .await - .unwrap(); - let mut harness = connect_supervisor_stream( - &state, - "legacy-sandbox", - openshell_core::proto::LEGACY_SUPERVISOR_PROTOCOL_REVISION, - ) - .await - .unwrap(); - let first = harness.inbound.message().await.unwrap().unwrap(); - let Some(gateway_message::Payload::SessionAccepted(accepted)) = first.payload else { - panic!("expected SessionAccepted"); - }; - assert!(accepted.bootstrap.is_none()); - publish_sandbox_components(&state, "legacy-sandbox", ConfigComponents::SANDBOX_CONFIG); - assert!( - tokio::time::timeout(Duration::from_millis(100), harness.inbound.message()) - .await - .is_err() - ); - } - - #[test] - fn bootstrap_requires_matching_provider_revision_fence() { - let mut bootstrap = ConfigBootstrap { - sandbox_config: Some(SandboxConfigSnapshot { - provider_env_revision: 7, - ..Default::default() - }), - provider_environment: Some(ProviderEnvironmentSnapshot { - provider_env_revision: 8, - ..Default::default() - }), - }; - assert!(!bootstrap_revisions_match(&bootstrap)); - bootstrap - .provider_environment - .as_mut() - .unwrap() - .provider_env_revision = 7; - assert!(bootstrap_revisions_match(&bootstrap)); - } - - #[tokio::test] - async fn stalled_credentials_do_not_block_session_acceptance() { - use openshell_core::proto::{CredentialHandle, Provider}; - - let mut state = test_server_state().await; - Arc::get_mut(&mut state) - .unwrap() - .config - .config_delivery_mode = ConfigDeliveryMode::Push; - state - .store - .put_message(&Provider { - metadata: Some(ObjectMeta { - id: "provider".into(), - name: "provider".into(), - workspace: "default".into(), - ..Default::default() - }), - r#type: "github".into(), - credential_handles: HashMap::from([( - "GITHUB_TOKEN".into(), - CredentialHandle { - driver: "test-static".into(), - handle: "blocked".into(), - ..Default::default() - }, - )]), - ..Default::default() - }) - .await - .unwrap(); - state - .store - .put_message(&Sandbox { - metadata: Some(ObjectMeta { - id: "sandbox".into(), - name: "sandbox".into(), - workspace: "default".into(), - ..Default::default() - }), - spec: Some(SandboxSpec { - providers: vec!["provider".into()], - ..Default::default() - }), - ..Default::default() - }) - .await - .unwrap(); - let (resolve_hit, _release_resolve) = state.credentials.gate_next_resolve(); - tokio::time::timeout(Duration::from_secs(10), async { - let connect = connect_supervisor_stream( - &state, - "sandbox", - openshell_core::proto::SUPERVISOR_PROTOCOL_REVISION, - ); - let (response, hit) = tokio::join!(connect, resolve_hit); - hit.expect("bootstrap must reach the stalled credential driver"); - let mut harness = response.unwrap(); - let first = harness.inbound.message().await.unwrap().unwrap(); - let Some(gateway_message::Payload::SessionAccepted(accepted)) = first.payload else { - panic!("expected session acceptance"); - }; - assert!(accepted.bootstrap.is_none()); - assert!( - state - .supervisor_sessions - .is_current_session("sandbox", &accepted.session_id) - ); - // Relay control remains usable while credential resolution is stalled. - let (_, relay) = state - .supervisor_sessions - .open_relay("sandbox", Duration::from_secs(1)) - .await - .unwrap(); - let message = harness.inbound.message().await.unwrap().unwrap(); - assert!(matches!( - message.payload, - Some(gateway_message::Payload::RelayOpen(_)) - )); - drop(relay); - }) - .await - .expect("optional bootstrap must not consume the relay reconnect budget"); - } -} diff --git a/crates/openshell-server/src/config_delivery/mod.rs b/crates/openshell-server/src/config_delivery/mod.rs new file mode 100644 index 000000000..b147926c0 --- /dev/null +++ b/crates/openshell-server/src/config_delivery/mod.rs @@ -0,0 +1,1431 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Build and route complete supervisor configuration snapshots. + +mod scheduler; +mod session_slots; + +use std::collections::{HashMap, HashSet}; +use std::fmt; +use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; +use std::time::{Duration, Instant}; + +use futures::StreamExt; +use metrics::counter; +use openshell_core::config::ConfigDeliveryMode; +use openshell_core::proto::{ + ConfigBootstrap, PeerConfigProviderTarget, PeerConfigSandboxTarget, + PeerConfigUpdateHintRequest, PeerConfigUpdateHintResponse, ProviderEnvironmentSnapshot, + Sandbox, SandboxConfigSnapshot, peer_config_update_hint_request, +}; +use tokio::sync::Notify; +use tokio::task::JoinSet; +use tonic::{Code, Request, Response, Status}; +use tracing::warn; + +use crate::ServerState; +use crate::auth::principal::Principal; +use crate::grpc::policy::{build_provider_environment_snapshot, build_sandbox_config_snapshot}; +use crate::supervisor_owner::{OWNER_TTL, SupervisorOwnerIndex}; +use crate::supervisor_session::SupervisorSessionRegistry; + +pub use scheduler::Registration; +use scheduler::{ + BuildOutcome, BuildTicket, DeliveryKey, FanoutScope, Lane, Limits, RouteTarget, RouteTicket, + Scheduler, Work, +}; +pub use session_slots::{ConfigSlots, SessionOutbound}; + +/// Leaves headroom below tonic's default 4 MiB decode limit for framing and +/// future envelope fields. +pub const MAX_SUPERVISOR_CONFIG_MESSAGE_BYTES: usize = 3 * 1024 * 1024; +const CONFIG_SNAPSHOT_BUILD_TIMEOUT: Duration = Duration::from_secs(45); +// Stage 1 bootstrap is optional. Keep credential backend stalls well below +// the 15-second relay session-wait budget while polling remains authoritative. +const CONFIG_BOOTSTRAP_BUILD_TIMEOUT: Duration = Duration::from_secs(1); +/// Concurrent snapshot builds allowed per pooled database connection. Builds +/// are short bursts of small queries, so a little oversubscription keeps the +/// pool busy without stacking every waiter on the acquire timeout. +const SNAPSHOT_BUILDS_PER_DB_CONNECTION: usize = 2; +const MIN_CONCURRENT_SNAPSHOT_BUILDS: usize = 4; +const MAX_CONCURRENT_ROUTES: usize = 8; +const PEER_HINT_TIMEOUT: Duration = Duration::from_secs(5); +const PEER_FANOUT_CONCURRENCY: usize = 8; + +/// One complete configuration component awaiting delivery to a supervisor. +#[derive(Clone)] +pub enum SupervisorConfigMessage { + SandboxConfig(Box), + ProviderEnvironment(ProviderEnvironmentSnapshot), +} + +impl SupervisorConfigMessage { + pub(crate) fn component(&self) -> ConfigComponentKind { + match self { + Self::SandboxConfig(_) => ConfigComponentKind::SandboxConfig, + Self::ProviderEnvironment(_) => ConfigComponentKind::ProviderEnvironment, + } + } +} + +impl fmt::Debug for SupervisorConfigMessage { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(match self { + Self::SandboxConfig(_) => "SandboxConfig()", + Self::ProviderEnvironment(_) => "ProviderEnvironment()", + }) + } +} + +/// Result of handing one configuration snapshot to a supervisor session. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DeliveryDisposition { + Queued, + /// Replaced an older snapshot the session had not sent yet. + Replaced, + NoActiveSession, + UnsupportedSession, + PayloadTooLarge, +} + +impl DeliveryDisposition { + fn metric_label(self) -> &'static str { + match self { + Self::Queued => "queued", + Self::Replaced => "replaced", + Self::NoActiveSession => "no_active_session", + Self::UnsupportedSession => "unsupported_session", + Self::PayloadTooLarge => "payload_too_large", + } + } +} + +/// Transport boundary for configuration delivery. +#[tonic::async_trait] +pub trait SupervisorConfigRouter: fmt::Debug + Send + Sync { + async fn deliver( + &self, + sandbox_id: &str, + session_id: &str, + message: SupervisorConfigMessage, + ) -> DeliveryDisposition; +} + +#[derive(Debug)] +pub struct LocalSupervisorConfigRouter { + sessions: Arc, +} + +impl LocalSupervisorConfigRouter { + #[must_use] + pub fn new(sessions: Arc) -> Self { + Self { sessions } + } +} + +#[tonic::async_trait] +impl SupervisorConfigRouter for LocalSupervisorConfigRouter { + async fn deliver( + &self, + sandbox_id: &str, + session_id: &str, + message: SupervisorConfigMessage, + ) -> DeliveryDisposition { + self.sessions + .deliver_config(sandbox_id, session_id, message) + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct ConfigComponents { + pub sandbox_config: bool, + pub provider_environment: bool, +} + +impl ConfigComponents { + pub const ALL: Self = Self { + sandbox_config: true, + provider_environment: true, + }; + + pub const SANDBOX_AND_PROVIDER: Self = Self { + sandbox_config: true, + provider_environment: true, + }; + + pub const SANDBOX_CONFIG: Self = Self { + sandbox_config: true, + provider_environment: false, + }; + + fn only(component: ConfigComponentKind) -> Self { + Self { + sandbox_config: component == ConfigComponentKind::SandboxConfig, + provider_environment: component == ConfigComponentKind::ProviderEnvironment, + } + } + + fn union(self, other: Self) -> Self { + Self { + sandbox_config: self.sandbox_config || other.sandbox_config, + provider_environment: self.provider_environment || other.provider_environment, + } + } + + fn is_empty(self) -> bool { + !self.sandbox_config && !self.provider_environment + } + + fn selected(self) -> impl Iterator { + [ + (self.sandbox_config, ConfigComponentKind::SandboxConfig), + ( + self.provider_environment, + ConfigComponentKind::ProviderEnvironment, + ), + ] + .into_iter() + .filter_map(|(selected, component)| selected.then_some(component)) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ConfigComponentKind { + SandboxConfig, + ProviderEnvironment, +} + +impl ConfigComponentKind { + fn name(self) -> &'static str { + match self { + Self::SandboxConfig => "sandbox_config", + Self::ProviderEnvironment => "provider_environment", + } + } +} + +/// Coalescing delivery scheduler shared by publications, supervisor sessions, +/// and one on-demand dispatcher task. +#[derive(Debug)] +pub struct ConfigDelivery { + state: Mutex, + wake: Notify, +} + +#[derive(Debug)] +struct DeliveryState { + scheduler: Scheduler, + dispatcher_running: bool, +} + +impl ConfigDelivery { + /// Size the build bound from the persistence pool that every build reads. + #[must_use] + pub fn for_db_connections(max_connections: u32) -> Self { + let max_connections = usize::try_from(max_connections).unwrap_or(usize::MAX); + let builds = max_connections + .saturating_mul(SNAPSHOT_BUILDS_PER_DB_CONNECTION) + .max(MIN_CONCURRENT_SNAPSHOT_BUILDS); + Self { + state: Mutex::new(DeliveryState { + scheduler: Scheduler::new(Limits::new(builds, MAX_CONCURRENT_ROUTES)), + dispatcher_running: false, + }), + wake: Notify::new(), + } + } + + pub(crate) fn current_seq(&self) -> u64 { + self.lock().scheduler.current_seq() + } + + fn with_scheduler(&self, update: impl FnOnce(&mut Scheduler) -> T) -> T { + update(&mut self.lock().scheduler) + } + + /// Delivery is best effort beside the session lifecycle, so a poisoned + /// lock must not turn session teardown into a panic. + fn lock(&self) -> MutexGuard<'_, DeliveryState> { + self.state.lock().unwrap_or_else(PoisonError::into_inner) + } +} + +pub async fn build_config_bootstrap( + state: &Arc, + sandbox: &Sandbox, +) -> Result { + tokio::time::timeout( + CONFIG_BOOTSTRAP_BUILD_TIMEOUT, + build_consistent_config_bootstrap(state, sandbox), + ) + .await + .map_err(|_| Status::deadline_exceeded("supervisor configuration bootstrap timed out"))? +} + +async fn build_consistent_config_bootstrap( + state: &Arc, + sandbox: &Sandbox, +) -> Result { + const MAX_BUILD_ATTEMPTS: usize = 3; + for _ in 0..MAX_BUILD_ATTEMPTS { + // Components are independent projections. The provider revision is a + // fence for the only overlapping input between sandbox configuration + // and provider environment state. + let (sandbox_config, provider_environment) = tokio::join!( + build_sandbox_config_snapshot(state, sandbox), + build_provider_environment_snapshot(state, sandbox, true), + ); + let bootstrap = ConfigBootstrap { + sandbox_config: Some(sandbox_config?), + provider_environment: Some(provider_environment?), + }; + if bootstrap_revisions_match(&bootstrap) { + return Ok(bootstrap); + } + counter!("openshell_supervisor_config_bootstrap_revision_mismatches_total").increment(1); + } + Err(Status::aborted( + "configuration changed while building supervisor bootstrap", + )) +} + +fn bootstrap_revisions_match(bootstrap: &ConfigBootstrap) -> bool { + bootstrap + .sandbox_config + .as_ref() + .zip(bootstrap.provider_environment.as_ref()) + .is_some_and(|(sandbox, provider)| { + sandbox.provider_env_revision == provider.provider_env_revision + }) +} + +fn push_enabled(state: &ServerState) -> bool { + state.config.config_delivery_mode == ConfigDeliveryMode::Push +} + +pub fn publish_sandbox_components( + state: &Arc, + sandbox_id: &str, + components: ConfigComponents, +) { + if !push_enabled(state) { + return; + } + let now = Instant::now(); + // A single-replica gateway owns every session, and registration covers a + // session that is still connecting. + let may_be_remote = !state.store.is_single_replica(); + state.config_delivery.with_scheduler(|scheduler| { + if !scheduler.publish_sandbox(sandbox_id, components, now) && may_be_remote { + scheduler.route(RouteTarget::Sandbox(sandbox_id.to_string()), components); + } + }); + kick(state); +} + +pub fn publish_workspace_components( + state: &Arc, + workspace: &str, + components: ConfigComponents, +) { + publish_fanout( + state, + &FanoutScope::Workspace(workspace.to_string()), + components, + ); +} + +/// Publish a provider change to the sandboxes that attach that provider. +pub fn publish_provider_components( + state: &Arc, + workspace: &str, + provider_name: &str, + components: ConfigComponents, +) { + publish_fanout( + state, + &FanoutScope::Provider { + workspace: workspace.to_string(), + name: provider_name.to_string(), + }, + components, + ); +} + +pub fn publish_all_connected(state: &Arc, components: ConfigComponents) { + publish_fanout(state, &FanoutScope::AllConnected, components); +} + +fn publish_fanout(state: &Arc, scope: &FanoutScope, components: ConfigComponents) { + if !push_enabled(state) { + return; + } + let notify_peers = !state.store.is_single_replica(); + state.config_delivery.with_scheduler(|scheduler| { + scheduler.publish_fanout(scope, components, notify_peers, Instant::now()); + }); + kick(state); +} + +/// Make a push-capable session visible to fanouts and sandbox updates. +pub fn register_session(state: &Arc, registration: Registration) { + state.config_delivery.with_scheduler(|scheduler| { + // A concurrent reconnect may already own the registry entry. Checking + // under the scheduler lock keeps the newest session registered. + if state + .supervisor_sessions + .is_current_session(®istration.sandbox_id, ®istration.session_id) + { + scheduler.register(registration, Instant::now()); + } + }); + kick(state); +} + +pub fn unregister_session(state: &ServerState, sandbox_id: &str, session_id: &str) { + state + .config_delivery + .with_scheduler(|scheduler| scheduler.unregister(sandbox_id, session_id)); +} + +fn kick(state: &Arc) { + let start = { + let mut delivery = state.config_delivery.lock(); + !std::mem::replace(&mut delivery.dispatcher_running, true) + }; + if start { + tokio::spawn(run_dispatcher(Arc::clone(state))); + } else { + state.config_delivery.wake.notify_one(); + } +} + +/// Releases outstanding work if the dispatcher unwinds, so a later +/// publication starts a replacement with consistent slot accounting. +struct DispatcherGuard { + state: Arc, + tickets: HashMap, + finished: bool, +} + +impl Drop for DispatcherGuard { + fn drop(&mut self) { + if self.finished { + return; + } + let mut delivery = self.state.config_delivery.lock(); + let now = Instant::now(); + for work in self.tickets.values() { + match work { + Work::Build(ticket) => { + delivery + .scheduler + .complete_build(ticket, BuildOutcome::Failed, now); + } + Work::Route(ticket) => delivery.scheduler.complete_route(ticket), + } + } + delivery.dispatcher_running = false; + } +} + +async fn run_dispatcher(state: Arc) { + let delivery = &state.config_delivery; + let mut tasks = JoinSet::new(); + let mut guard = DispatcherGuard { + state: Arc::clone(&state), + tickets: HashMap::new(), + finished: false, + }; + loop { + let started = { + let mut delivery_state = delivery.lock(); + let now = Instant::now(); + let mut started = Vec::new(); + while let Some(work) = delivery_state.scheduler.next_work(now) { + started.push(work); + } + delivery_state.scheduler.record_gauges(); + // Clearing the flag under the scheduler lock means a concurrent + // publication either sees this dispatcher running or starts one. + if started.is_empty() && !delivery_state.scheduler.has_running_work() { + delivery_state.dispatcher_running = false; + guard.finished = true; + return; + } + started + }; + for work in started { + let handle = tasks.spawn(run_work(Arc::clone(&state), work.clone())); + guard.tickets.insert(handle.id(), work); + } + tokio::select! { + joined = tasks.join_next_with_id(), if !tasks.is_empty() => { + let (id, result) = match joined.expect("join set is not empty") { + Ok((id, result)) => (id, Some(result)), + Err(error) => { + // Panic payloads may contain credential backend data. + warn!( + cancelled = error.is_cancelled(), + panicked = error.is_panic(), + "supervisor configuration delivery task failed" + ); + (error.id(), None) + } + }; + let work = guard.tickets.remove(&id).expect("delivery task has a ticket"); + complete_work(&state, &work, result); + } + () = delivery.wake.notified() => {} + } + } +} + +enum WorkResult { + Build(BuildResult), + Route(RouteResult), +} + +struct BuildResult { + outcome: BuildOutcome, + /// The session moved to another gateway after the build started. + reroute: bool, +} + +enum RouteResult { + Done, + /// This gateway owns the current session for the routed sandbox. + Local, +} + +async fn run_work(state: Arc, work: Work) -> WorkResult { + match work { + Work::Build(ticket) => WorkResult::Build(run_build(&state, &ticket).await), + Work::Route(ticket) => WorkResult::Route(run_route(&state, &ticket).await), + } +} + +fn complete_work(state: &ServerState, work: &Work, result: Option) { + let now = Instant::now(); + state + .config_delivery + .with_scheduler(|scheduler| match (work, result) { + (Work::Build(ticket), Some(WorkResult::Build(result))) => { + scheduler.complete_build(ticket, result.outcome, now); + if result.reroute { + scheduler.route( + RouteTarget::Sandbox(ticket.key.sandbox_id.clone()), + ConfigComponents::only(ticket.key.component), + ); + } + } + (Work::Build(ticket), _) => { + scheduler.complete_build(ticket, BuildOutcome::Failed, now); + } + (Work::Route(ticket), result) => { + scheduler.complete_route(ticket); + if let ( + RouteTarget::Sandbox(sandbox_id), + Some(WorkResult::Route(RouteResult::Local)), + ) = (&ticket.target, result) + { + scheduler.publish_sandbox(sandbox_id, ticket.components, now); + } + } + }); +} + +async fn run_build(state: &Arc, ticket: &BuildTicket) -> BuildResult { + let BuildTicket { + key, + lane, + session_id, + .. + } = ticket; + let failed = BuildResult { + outcome: BuildOutcome::Failed, + reroute: false, + }; + let built = + tokio::time::timeout(CONFIG_SNAPSHOT_BUILD_TIMEOUT, build_component(state, key)).await; + let (message, providers) = match built { + Ok(Ok(Some(built))) => built, + Ok(Ok(None)) => { + record_build(key.component, *lane, "ok"); + return BuildResult { + outcome: BuildOutcome::Built { providers: None }, + reroute: false, + }; + } + Ok(Err(error)) => { + record_build_failure(key, *lane, "failed", error.code()); + return failed; + } + Err(_) => { + record_build_failure(key, *lane, "timeout", Code::DeadlineExceeded); + return failed; + } + }; + record_build(key.component, *lane, "ok"); + let reroute = match owner_check(state, &key.sandbox_id, session_id).await { + OwnerCheck::Current => { + let disposition = state + .supervisor_config_router() + .deliver(&key.sandbox_id, session_id, message) + .await; + record_delivery(key.component, disposition.metric_label()); + false + } + OwnerCheck::Remote => { + record_delivery(key.component, "owner_moved"); + true + } + OwnerCheck::Gone => { + record_delivery(key.component, "no_active_session"); + false + } + OwnerCheck::Unknown => { + record_delivery(key.component, "owner_lookup_failed"); + return failed; + } + }; + BuildResult { + outcome: BuildOutcome::Built { + providers: Some(providers), + }, + reroute, + } +} + +async fn build_component( + state: &Arc, + key: &DeliveryKey, +) -> Result)>, Status> { + let Some(sandbox) = state + .store + .get_message::(&key.sandbox_id) + .await + .map_err(|error| Status::internal(format!("fetch sandbox failed: {error}")))? + else { + return Ok(None); + }; + let providers = sandbox + .spec + .as_ref() + .map(|spec| spec.providers.iter().cloned().collect()) + .unwrap_or_default(); + let message = match key.component { + ConfigComponentKind::SandboxConfig => SupervisorConfigMessage::SandboxConfig(Box::new( + build_sandbox_config_snapshot(state, &sandbox).await?, + )), + ConfigComponentKind::ProviderEnvironment => SupervisorConfigMessage::ProviderEnvironment( + build_provider_environment_snapshot(state, &sandbox, true).await?, + ), + }; + Ok(Some((message, providers))) +} + +enum OwnerCheck { + /// This gateway owns the session the build was dispatched for. + Current, + Remote, + Gone, + Unknown, +} + +async fn owner_check(state: &Arc, sandbox_id: &str, session_id: &str) -> OwnerCheck { + let owners = SupervisorOwnerIndex::new(Arc::clone(&state.store), OWNER_TTL); + match owners.read(sandbox_id).await { + Ok(Some(owner)) if owner.is_fresh(OWNER_TTL) => { + if owner.owner_replica_id != state.replica_id { + OwnerCheck::Remote + } else if owner.session_id == session_id { + OwnerCheck::Current + } else { + OwnerCheck::Gone + } + } + Ok(_) => OwnerCheck::Gone, + Err(error) => { + warn!(sandbox_id, error = %error, "configuration owner lookup failed"); + OwnerCheck::Unknown + } + } +} + +async fn run_route(state: &Arc, ticket: &RouteTicket) -> RouteResult { + match &ticket.target { + RouteTarget::Sandbox(sandbox_id) => { + route_sandbox_hint(state, sandbox_id, ticket.components).await + } + RouteTarget::Peers(scope) => { + notify_peers(state, scope, ticket.components).await; + RouteResult::Done + } + } +} + +/// Send a secret-free hint to the gateway that owns the sandbox session. The +/// owner builds its own snapshot from shared state. +async fn route_sandbox_hint( + state: &Arc, + sandbox_id: &str, + components: ConfigComponents, +) -> RouteResult { + let owners = SupervisorOwnerIndex::new(Arc::clone(&state.store), OWNER_TTL); + let mut final_outcome = "stale_owner"; + for attempt in 0..2 { + let owner = match owners.read(sandbox_id).await { + Ok(Some(owner)) if owner.is_fresh(OWNER_TTL) => owner, + Ok(_) => return RouteResult::Done, + Err(error) => { + warn!(sandbox_id, error = %error, "configuration owner lookup failed"); + return RouteResult::Done; + } + }; + if owner.owner_replica_id == state.replica_id { + return if state + .supervisor_sessions + .is_current_session(sandbox_id, &owner.session_id) + { + RouteResult::Local + } else { + RouteResult::Done + }; + } + if !is_peer_endpoint(&owner.owner_peer_endpoint) { + warn!(sandbox_id, owner = %owner.owner_replica_id, "configuration owner has no reachable peer endpoint"); + return RouteResult::Done; + } + let request = PeerConfigUpdateHintRequest { + scope: Some(peer_config_update_hint_request::Scope::Sandbox( + PeerConfigSandboxTarget { + sandbox_id: sandbox_id.to_string(), + session_id: owner.session_id, + }, + )), + sandbox_config: components.sandbox_config, + provider_environment: components.provider_environment, + }; + match send_peer_hint(state, &owner.owner_peer_endpoint, request).await { + HintOutcome::StaleOwner => final_outcome = "stale_owner", + HintOutcome::Failed(outcome) => { + final_outcome = outcome; + warn!(sandbox_id, owner = %owner.owner_replica_id, attempt, outcome, "configuration peer hint failed"); + } + outcome => { + record_peer_hint(outcome.label()); + return RouteResult::Done; + } + } + } + record_peer_hint(final_outcome); + RouteResult::Done +} + +async fn notify_peers(state: &Arc, scope: &FanoutScope, components: ConfigComponents) { + let owners = SupervisorOwnerIndex::new(Arc::clone(&state.store), OWNER_TTL); + let endpoints = match owners.list_fresh_peer_endpoints(&state.replica_id).await { + Ok(endpoints) => endpoints, + Err(error) => { + warn!(error = %error, "configuration peer fanout owner listing failed"); + return; + } + }; + let scope = peer_scope(scope); + futures::stream::iter(endpoints) + .for_each_concurrent(PEER_FANOUT_CONCURRENCY, |endpoint| { + let request = PeerConfigUpdateHintRequest { + scope: Some(scope.clone()), + sandbox_config: components.sandbox_config, + provider_environment: components.provider_environment, + }; + async move { + let outcome = send_peer_hint(state, &endpoint, request).await; + if let HintOutcome::Failed(outcome) = outcome { + warn!(endpoint = %endpoint, outcome, "configuration peer fanout hint failed"); + } + record_peer_hint(outcome.label()); + } + }) + .await; +} + +fn peer_scope(scope: &FanoutScope) -> peer_config_update_hint_request::Scope { + match scope { + FanoutScope::AllConnected => peer_config_update_hint_request::Scope::AllConnected(true), + FanoutScope::Workspace(workspace) => { + peer_config_update_hint_request::Scope::Workspace(workspace.clone()) + } + FanoutScope::Provider { workspace, name } => { + peer_config_update_hint_request::Scope::Provider(PeerConfigProviderTarget { + workspace: workspace.clone(), + name: name.clone(), + }) + } + } +} + +fn is_peer_endpoint(endpoint: &str) -> bool { + endpoint.starts_with("http://") || endpoint.starts_with("https://") +} + +enum HintOutcome { + Accepted, + StaleOwner, + UnsupportedPeer, + Failed(&'static str), +} + +impl HintOutcome { + fn label(&self) -> &'static str { + match self { + Self::Accepted => "accepted", + Self::StaleOwner => "stale_owner", + Self::UnsupportedPeer => "unsupported_peer", + Self::Failed(outcome) => outcome, + } + } +} + +async fn send_peer_hint( + state: &Arc, + endpoint: &str, + request: PeerConfigUpdateHintRequest, +) -> HintOutcome { + match tokio::time::timeout( + PEER_HINT_TIMEOUT, + crate::supervisor_session::forward_config_hint_to_peer(state, endpoint, request), + ) + .await + { + Ok(Ok(response)) if response.stale_owner => HintOutcome::StaleOwner, + Ok(Ok(_)) => HintOutcome::Accepted, + Ok(Err(error)) if error.code() == Code::Unimplemented => HintOutcome::UnsupportedPeer, + Ok(Err(_)) => HintOutcome::Failed("peer_error"), + Err(_) => HintOutcome::Failed("timeout"), + } +} + +pub fn handle_peer_config_update_hint( + state: &Arc, + request: Request, +) -> Result, Status> { + if !matches!( + request.extensions().get::(), + Some(Principal::Peer(_)) + ) { + return Err(Status::permission_denied("gateway peer principal required")); + } + if !push_enabled(state) { + return Err(Status::failed_precondition( + "configuration push is disabled", + )); + } + let hint = request.into_inner(); + let components = ConfigComponents { + sandbox_config: hint.sandbox_config, + provider_environment: hint.provider_environment, + }; + if components.is_empty() { + return Err(Status::invalid_argument( + "at least one configuration component is required", + )); + } + let mut response = PeerConfigUpdateHintResponse::default(); + // Hints from peers never fan out to peers again. + let scope = match hint.scope { + Some(peer_config_update_hint_request::Scope::Sandbox(target)) => { + if target.sandbox_id.is_empty() || target.session_id.is_empty() { + return Err(Status::invalid_argument( + "sandbox and session IDs are required", + )); + } + // The sender resolved this session from the owner index. The + // owner check after the build guards against a later move. + if state + .supervisor_sessions + .is_current_session(&target.sandbox_id, &target.session_id) + { + state.config_delivery.with_scheduler(|scheduler| { + scheduler.publish_sandbox(&target.sandbox_id, components, Instant::now()) + }); + kick(state); + } else { + response.stale_owner = true; + } + return Ok(Response::new(response)); + } + Some(peer_config_update_hint_request::Scope::Workspace(workspace)) => { + if workspace.is_empty() { + return Err(Status::invalid_argument("workspace is required")); + } + FanoutScope::Workspace(workspace) + } + Some(peer_config_update_hint_request::Scope::Provider(target)) => { + if target.workspace.is_empty() || target.name.is_empty() { + return Err(Status::invalid_argument( + "provider workspace and name are required", + )); + } + FanoutScope::Provider { + workspace: target.workspace, + name: target.name, + } + } + Some(peer_config_update_hint_request::Scope::AllConnected(true)) => { + FanoutScope::AllConnected + } + _ => { + return Err(Status::invalid_argument( + "configuration hint scope is required", + )); + } + }; + state.config_delivery.with_scheduler(|scheduler| { + scheduler.publish_fanout(&scope, components, false, Instant::now()); + }); + kick(state); + Ok(Response::new(response)) +} + +/// Supervisor session registered for configuration push without a gRPC stream. +#[cfg(test)] +pub struct TestPushSession { + _control: tokio::sync::mpsc::Sender, + pub outbound: SessionOutbound, +} + +#[cfg(test)] +pub fn register_test_push_session( + state: &Arc, + sandbox: &Sandbox, + session_id: &str, +) -> TestPushSession { + use crate::persistence::{ObjectId, ObjectWorkspace}; + + let (control, rx) = tokio::sync::mpsc::channel(4); + let slots = Arc::new(ConfigSlots::default()); + let sandbox_id = sandbox.object_id().to_string(); + state.supervisor_sessions.register_with_config_slots( + sandbox_id.clone(), + session_id.to_string(), + control.clone(), + tokio::sync::oneshot::channel().0, + Some(Arc::clone(&slots)), + ); + register_session( + state, + Registration { + sandbox_id, + session_id: session_id.to_string(), + workspace: sandbox.object_workspace().to_string(), + providers: sandbox + .spec + .as_ref() + .map(|spec| spec.providers.iter().cloned().collect()) + .unwrap_or_default(), + captured_seq: state.config_delivery.current_seq(), + bootstrap_built: true, + }, + ); + TestPushSession { + _control: control, + outbound: SessionOutbound::new(rx, slots), + } +} + +fn record_build(component: ConfigComponentKind, lane: Lane, outcome: &'static str) { + counter!( + "openshell_supervisor_config_builds_total", + "component" => component.name(), + "lane" => lane.name(), + "outcome" => outcome, + ) + .increment(1); +} + +fn record_build_failure(key: &DeliveryKey, lane: Lane, outcome: &'static str, error_code: Code) { + record_build(key.component, lane, outcome); + warn!( + sandbox_id = %key.sandbox_id, + component = key.component.name(), + ?error_code, + "failed to build supervisor configuration snapshot" + ); +} + +fn record_delivery(component: ConfigComponentKind, outcome: &'static str) { + counter!( + "openshell_supervisor_config_deliveries_total", + "component" => component.name(), + "outcome" => outcome, + ) + .increment(1); +} + +fn record_peer_hint(outcome: &'static str) { + counter!("openshell_supervisor_config_peer_hints_total", "outcome" => outcome).increment(1); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::grpc::test_support::{ + SupervisorStreamHarness, connect_supervisor_stream, test_server_state, + }; + use openshell_core::proto::{ + GatewayMessage, ObjectMeta, Provider, SandboxSpec, gateway_message, + }; + + fn peer_request( + scope: peer_config_update_hint_request::Scope, + ) -> Request { + let mut request = Request::new(PeerConfigUpdateHintRequest { + scope: Some(scope), + sandbox_config: true, + provider_environment: false, + }); + request + .extensions_mut() + .insert(Principal::Peer(crate::auth::principal::PeerPrincipal { + replica_id: "other-replica".into(), + pod_uid: "peer-pod".into(), + })); + request + } + + fn peer_hint(sandbox_id: &str, session_id: &str) -> Request { + peer_request(peer_config_update_hint_request::Scope::Sandbox( + PeerConfigSandboxTarget { + sandbox_id: sandbox_id.into(), + session_id: session_id.into(), + }, + )) + } + + async fn push_state() -> Arc { + let mut state = test_server_state().await; + Arc::get_mut(&mut state) + .unwrap() + .config + .config_delivery_mode = ConfigDeliveryMode::Push; + state + } + + async fn put_sandbox(state: &ServerState, sandbox_id: &str, providers: &[&str]) { + state + .store + .put_message(&Sandbox { + metadata: Some(ObjectMeta { + id: sandbox_id.into(), + name: sandbox_id.into(), + workspace: "default".into(), + ..Default::default() + }), + spec: Some(SandboxSpec { + providers: providers.iter().map(ToString::to_string).collect(), + ..Default::default() + }), + ..Default::default() + }) + .await + .unwrap(); + } + + async fn put_provider(state: &ServerState, name: &str) { + state + .store + .put_message(&Provider { + metadata: Some(ObjectMeta { + id: format!("{name}-id"), + name: name.into(), + workspace: "default".into(), + ..Default::default() + }), + r#type: "github".into(), + ..Default::default() + }) + .await + .unwrap(); + } + + async fn accepted_session( + state: &Arc, + sandbox_id: &str, + ) -> (SupervisorStreamHarness, String) { + let mut harness = connect_supervisor_stream( + state, + sandbox_id, + openshell_core::proto::SUPERVISOR_PROTOCOL_REVISION, + ) + .await + .unwrap(); + let first = tokio::time::timeout(Duration::from_secs(5), harness.inbound.message()) + .await + .unwrap() + .unwrap() + .unwrap(); + let Some(gateway_message::Payload::SessionAccepted(accepted)) = first.payload else { + panic!("expected SessionAccepted"); + }; + (harness, accepted.session_id) + } + + async fn next_update( + harness: &mut SupervisorStreamHarness, + wait: Duration, + ) -> Option { + tokio::time::timeout(wait, harness.inbound.message()) + .await + .ok() + .map(|message| message.unwrap().unwrap()) + } + + fn is_config_update(message: &GatewayMessage) -> bool { + matches!( + message.payload, + Some(gateway_message::Payload::ConfigUpdate(_)) + ) + } + + #[tokio::test] + async fn peer_hint_requires_push_and_current_session() { + let state = test_server_state().await; + let error = handle_peer_config_update_hint(&state, peer_hint("sandbox", "old-session")) + .unwrap_err(); + assert_eq!(error.code(), Code::FailedPrecondition); + + let state = push_state().await; + let response = handle_peer_config_update_hint(&state, peer_hint("sandbox", "old-session")) + .unwrap() + .into_inner(); + assert!(response.stale_owner); + + let error = handle_peer_config_update_hint( + &state, + Request::new(PeerConfigUpdateHintRequest::default()), + ) + .unwrap_err(); + assert_eq!(error.code(), Code::PermissionDenied); + + let error = handle_peer_config_update_hint( + &state, + peer_request(peer_config_update_hint_request::Scope::Provider( + PeerConfigProviderTarget { + workspace: "default".into(), + name: String::new(), + }, + )), + ) + .unwrap_err(); + assert_eq!(error.code(), Code::InvalidArgument); + } + + #[tokio::test] + async fn peer_hint_rebuilds_snapshot_on_the_session_owner() { + let state = push_state().await; + put_sandbox(&state, "owned-sandbox", &[]).await; + let (mut harness, session_id) = accepted_session(&state, "owned-sandbox").await; + + let response = + handle_peer_config_update_hint(&state, peer_hint("owned-sandbox", &session_id)) + .unwrap() + .into_inner(); + assert!(!response.stale_owner); + let update = next_update(&mut harness, Duration::from_secs(5)) + .await + .expect("configuration update"); + assert!(is_config_update(&update)); + } + + #[tokio::test] + async fn provider_change_reaches_only_attached_sessions() { + let state = push_state().await; + put_provider(&state, "github").await; + put_sandbox(&state, "attached", &["github"]).await; + put_sandbox(&state, "unattached", &[]).await; + let (mut attached, _) = accepted_session(&state, "attached").await; + let (mut unattached, _) = accepted_session(&state, "unattached").await; + + publish_provider_components(&state, "default", "github", ConfigComponents::ALL); + + for _ in 0..2 { + let update = next_update(&mut attached, Duration::from_secs(5)) + .await + .expect("attached sandbox receives both components"); + assert!(is_config_update(&update)); + } + assert!( + next_update(&mut unattached, Duration::from_millis(200)) + .await + .is_none() + ); + + publish_workspace_components(&state, "default", ConfigComponents::SANDBOX_CONFIG); + assert!( + next_update(&mut unattached, Duration::from_secs(5)) + .await + .is_some_and(|update| is_config_update(&update)) + ); + } + + fn has_recipient(state: &ServerState, sandbox_id: &str) -> bool { + state + .config_delivery + .with_scheduler(|scheduler| scheduler.has_recipient(sandbox_id)) + } + + #[tokio::test] + async fn poll_mode_leaves_delivery_dormant() { + let state = test_server_state().await; + put_provider(&state, "github").await; + put_sandbox(&state, "sandbox", &["github"]).await; + let (mut harness, _) = accepted_session(&state, "sandbox").await; + assert!(!has_recipient(&state, "sandbox")); + + publish_sandbox_components(&state, "sandbox", ConfigComponents::ALL); + publish_workspace_components(&state, "default", ConfigComponents::ALL); + publish_provider_components(&state, "default", "github", ConfigComponents::ALL); + publish_all_connected(&state, ConfigComponents::ALL); + + assert_eq!(state.config_delivery.current_seq(), 0); + assert!(!state.config_delivery.lock().dispatcher_running); + assert!( + next_update(&mut harness, Duration::from_millis(100)) + .await + .is_none() + ); + } + + #[tokio::test] + async fn stale_local_session_gets_nothing_after_its_owner_moves() { + let state = push_state().await; + put_sandbox(&state, "moved", &[]).await; + let (mut harness, _) = accepted_session(&state, "moved").await; + assert!(has_recipient(&state, "moved")); + SupervisorOwnerIndex::new(Arc::clone(&state.store), OWNER_TTL) + .publish( + "moved", + "remote-session", + "instance", + 1, + "other-replica", + "http://127.0.0.1:9", + ) + .await + .unwrap(); + + publish_sandbox_components(&state, "moved", ConfigComponents::SANDBOX_CONFIG); + assert!( + next_update(&mut harness, Duration::from_millis(500)) + .await + .is_none() + ); + } + + #[tokio::test] + async fn disconnected_sessions_leave_the_scheduler() { + let state = push_state().await; + put_sandbox(&state, "sandbox", &[]).await; + let (_harness, _) = accepted_session(&state, "sandbox").await; + assert!(has_recipient(&state, "sandbox")); + + assert!(state.supervisor_sessions.disconnect("sandbox")); + tokio::time::timeout(Duration::from_secs(5), async { + while has_recipient(&state, "sandbox") { + tokio::time::sleep(Duration::from_millis(10)).await; + } + }) + .await + .expect("session cleanup unregisters the recipient"); + } + + #[tokio::test] + async fn build_for_a_moved_session_routes_to_the_new_owner() { + let state = push_state().await; + let work = state.config_delivery.with_scheduler(|scheduler| { + scheduler.register( + Registration { + sandbox_id: "moved".into(), + session_id: "session".into(), + workspace: "default".into(), + providers: HashSet::new(), + captured_seq: 0, + bootstrap_built: true, + }, + Instant::now(), + ); + scheduler.publish_sandbox("moved", ConfigComponents::SANDBOX_CONFIG, Instant::now()); + scheduler.next_work(Instant::now()).unwrap() + }); + + complete_work( + &state, + &work, + Some(WorkResult::Build(BuildResult { + outcome: BuildOutcome::Built { providers: None }, + reroute: true, + })), + ); + let next = state + .config_delivery + .with_scheduler(|scheduler| scheduler.next_work(Instant::now())); + assert_eq!( + next, + Some(Work::Route(RouteTicket { + target: RouteTarget::Sandbox("moved".into()), + components: ConfigComponents::SANDBOX_CONFIG, + })) + ); + } + + #[test] + fn build_slots_are_sized_from_the_database_pool() { + let builds = |connections| { + ConfigDelivery::for_db_connections(connections) + .with_scheduler(|scheduler| scheduler.limits().builds()) + }; + assert_eq!(builds(10), 20); + assert_eq!(builds(1), MIN_CONCURRENT_SNAPSHOT_BUILDS); + } + + #[test] + fn configuration_message_debug_output_redacts_payloads() { + let message = SupervisorConfigMessage::ProviderEnvironment(ProviderEnvironmentSnapshot { + values: vec![openshell_core::proto::ProviderEnvironmentValue { + name: "TOKEN".into(), + value: "secret-marker".into(), + ..Default::default() + }], + ..Default::default() + }); + assert!(!format!("{message:?}").contains("secret-marker")); + } + + #[tokio::test] + async fn session_acceptance_precedes_live_configuration_updates() { + let state = push_state().await; + put_sandbox(&state, "sandbox", &[]).await; + let (mut harness, _) = accepted_session(&state, "sandbox").await; + assert!(has_recipient(&state, "sandbox")); + + publish_sandbox_components(&state, "sandbox", ConfigComponents::SANDBOX_CONFIG); + let update = next_update(&mut harness, Duration::from_secs(5)) + .await + .expect("configuration update"); + assert!(is_config_update(&update)); + } + + #[tokio::test] + async fn legacy_supervisor_keeps_polling_when_gateway_shadow_push_is_enabled() { + let state = push_state().await; + put_sandbox(&state, "legacy-sandbox", &[]).await; + let mut harness = connect_supervisor_stream( + &state, + "legacy-sandbox", + openshell_core::proto::LEGACY_SUPERVISOR_PROTOCOL_REVISION, + ) + .await + .unwrap(); + let first = harness.inbound.message().await.unwrap().unwrap(); + let Some(gateway_message::Payload::SessionAccepted(accepted)) = first.payload else { + panic!("expected SessionAccepted"); + }; + assert!(accepted.bootstrap.is_none()); + publish_sandbox_components(&state, "legacy-sandbox", ConfigComponents::SANDBOX_CONFIG); + publish_all_connected(&state, ConfigComponents::ALL); + assert!( + next_update(&mut harness, Duration::from_millis(100)) + .await + .is_none() + ); + } + + #[test] + fn bootstrap_requires_matching_provider_revision_fence() { + let mut bootstrap = ConfigBootstrap { + sandbox_config: Some(SandboxConfigSnapshot { + provider_env_revision: 7, + ..Default::default() + }), + provider_environment: Some(ProviderEnvironmentSnapshot { + provider_env_revision: 8, + ..Default::default() + }), + }; + assert!(!bootstrap_revisions_match(&bootstrap)); + bootstrap + .provider_environment + .as_mut() + .unwrap() + .provider_env_revision = 7; + assert!(bootstrap_revisions_match(&bootstrap)); + } + + #[tokio::test] + async fn stalled_credentials_do_not_block_session_acceptance() { + use openshell_core::proto::CredentialHandle; + + let state = push_state().await; + state + .store + .put_message(&Provider { + metadata: Some(ObjectMeta { + id: "provider".into(), + name: "provider".into(), + workspace: "default".into(), + ..Default::default() + }), + r#type: "github".into(), + credential_handles: HashMap::from([( + "GITHUB_TOKEN".into(), + CredentialHandle { + driver: "test-static".into(), + handle: "blocked".into(), + ..Default::default() + }, + )]), + ..Default::default() + }) + .await + .unwrap(); + put_sandbox(&state, "sandbox", &["provider"]).await; + let (resolve_hit, _release_resolve) = state.credentials.gate_next_resolve(); + tokio::time::timeout(Duration::from_secs(10), async { + let connect = connect_supervisor_stream( + &state, + "sandbox", + openshell_core::proto::SUPERVISOR_PROTOCOL_REVISION, + ); + let (response, hit) = tokio::join!(connect, resolve_hit); + hit.expect("bootstrap must reach the stalled credential driver"); + let mut harness = response.unwrap(); + let first = harness.inbound.message().await.unwrap().unwrap(); + let Some(gateway_message::Payload::SessionAccepted(accepted)) = first.payload else { + panic!("expected session acceptance"); + }; + assert!(accepted.bootstrap.is_none()); + assert!( + state + .supervisor_sessions + .is_current_session("sandbox", &accepted.session_id) + ); + // Relay control remains usable while credential resolution is stalled. + let (_, relay) = state + .supervisor_sessions + .open_relay("sandbox", Duration::from_secs(1)) + .await + .unwrap(); + let message = harness.inbound.message().await.unwrap().unwrap(); + assert!(matches!( + message.payload, + Some(gateway_message::Payload::RelayOpen(_)) + )); + drop(relay); + }) + .await + .expect("optional bootstrap must not consume the relay reconnect budget"); + } +} diff --git a/crates/openshell-server/src/config_delivery/scheduler.rs b/crates/openshell-server/src/config_delivery/scheduler.rs new file mode 100644 index 000000000..cf3c63c75 --- /dev/null +++ b/crates/openshell-server/src/config_delivery/scheduler.rs @@ -0,0 +1,1342 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Synchronous scheduling policy for supervisor configuration delivery. +//! +//! Every publication takes a sequence number. When a build is dispatched, its +//! key records the highest sequence issued so far. Publications are issued +//! after their commits, so that build observes state at least as new as every +//! publication it covers, and a key needs work for publication `P` only while +//! its `last_build_seq < P`. +//! +//! Admission never rejects: every key set is bounded by sandboxes, workspaces, +//! and providers, so repeated publications only coalesce. Fanouts are cursors +//! over the sorted local recipients rather than queued keys, and build slots +//! are granted only here, with a reserve for sandbox-scoped work. + +use std::collections::hash_map::Entry; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}; +use std::ops::Bound; +use std::time::Instant; + +use metrics::{counter, gauge, histogram}; + +use super::{ConfigComponentKind, ConfigComponents}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct DeliveryKey { + pub sandbox_id: String, + pub component: ConfigComponentKind, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum FanoutScope { + AllConnected, + Workspace(String), + /// Sandboxes in `workspace` that attach the named provider. + Provider { + workspace: String, + name: String, + }, +} + +impl FanoutScope { + fn includes(&self, other: &Self) -> bool { + match (self, other) { + (Self::AllConnected, _) => true, + ( + Self::Workspace(workspace), + Self::Workspace(other) + | Self::Provider { + workspace: other, .. + }, + ) => workspace == other, + (Self::Provider { .. }, Self::Provider { .. }) => self == other, + _ => false, + } + } + + fn workspace(&self) -> Option<&str> { + match self { + Self::AllConnected => None, + Self::Workspace(workspace) | Self::Provider { workspace, .. } => Some(workspace), + } + } + + fn metric_label(&self) -> &'static str { + match self { + Self::AllConnected => "all_connected", + Self::Workspace(_) => "workspace", + Self::Provider { .. } => "provider", + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct FanoutKey { + scope: FanoutScope, + component: ConfigComponentKind, +} + +/// Lanes are ordered by priority. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Lane { + Fanout, + Direct, +} + +impl Lane { + pub(crate) fn name(self) -> &'static str { + match self { + Self::Fanout => "fanout", + Self::Direct => "direct", + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Limits { + builds: usize, + direct_reserve: usize, + routes: usize, +} + +impl Limits { + /// Fanout work may use every build slot except `direct_reserve`, and + /// sandbox-scoped work leaves one slot for waiting fanout work. + pub(crate) fn new(builds: usize, routes: usize) -> Self { + let builds = builds.max(2); + Self { + builds, + direct_reserve: (builds / 4).clamp(1, builds - 1), + routes: routes.max(1), + } + } + + #[cfg(test)] + pub(crate) fn builds(self) -> usize { + self.builds + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Work { + Build(BuildTicket), + Route(RouteTicket), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BuildTicket { + pub key: DeliveryKey, + pub lane: Lane, + /// Session registered when the build was dispatched. Delivery must not + /// cross into a replacement session, which received its own bootstrap. + pub session_id: String, + epoch: u64, + seq: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum RouteTarget { + /// Sandbox without a local push recipient; its owner may be a peer. + Sandbox(String), + /// Scope hint for every peer gateway. + Peers(FanoutScope), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RouteTicket { + pub target: RouteTarget, + pub components: ConfigComponents, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BuildOutcome { + /// `providers` is the attachment list read with the snapshot, when the + /// sandbox still exists. + Built { + providers: Option>, + }, + Failed, +} + +#[derive(Debug, Clone)] +pub struct Registration { + pub sandbox_id: String, + pub session_id: String, + pub workspace: String, + pub providers: HashSet, + /// `current_seq()` observed before the bootstrap build started. + pub captured_seq: u64, + pub bootstrap_built: bool, +} + +#[derive(Debug, Default)] +struct KeyState { + last_build_seq: u64, + rollback_seq: u64, + in_flight: bool, + queued: Option, + requeue: Option, + /// A fanout skipped this in-flight key as current; rebuild if it fails. + requeue_on_failure: Option, + waiting_since: Option, +} + +#[derive(Debug)] +struct Recipient { + session_id: String, + epoch: u64, + workspace: String, + providers: HashSet, + /// Dispatch sequence of the build that last refreshed `providers`. Builds + /// can finish out of order, and an older attachment list must not win. + providers_seq: u64, + sandbox_config: KeyState, + provider_environment: KeyState, +} + +impl Recipient { + fn key(&self, component: ConfigComponentKind) -> &KeyState { + match component { + ConfigComponentKind::SandboxConfig => &self.sandbox_config, + ConfigComponentKind::ProviderEnvironment => &self.provider_environment, + } + } + + fn key_mut(&mut self, component: ConfigComponentKind) -> &mut KeyState { + match component { + ConfigComponentKind::SandboxConfig => &mut self.sandbox_config, + ConfigComponentKind::ProviderEnvironment => &mut self.provider_environment, + } + } +} + +#[derive(Debug)] +struct Cursor { + target_seq: u64, + /// Last sandbox handed out or skipped in sorted order. + position: Option, + /// Position at the latest publication. After wrapping, the pass ends here. + lap_end: Option, + wrapped: bool, + started: Instant, +} + +#[derive(Debug, Default)] +struct RouteState { + pending: ConfigComponents, + queued: bool, + in_flight: bool, +} + +#[derive(Debug, Default)] +struct Running { + direct: usize, + fanout: usize, + routes: usize, +} + +impl Running { + fn builds(&self) -> usize { + self.direct + self.fanout + } + + fn lane_mut(&mut self, lane: Lane) -> &mut usize { + match lane { + Lane::Direct => &mut self.direct, + Lane::Fanout => &mut self.fanout, + } + } +} + +#[derive(Debug, Default)] +struct QueuedCounts { + direct: usize, + fanout: usize, +} + +impl QueuedCounts { + fn transition(&mut self, from: Option, to: Option) { + if let Some(lane) = from { + *self.lane_mut(lane) -= 1; + } + if let Some(lane) = to { + *self.lane_mut(lane) += 1; + } + } + + fn lane_mut(&mut self, lane: Lane) -> &mut usize { + match lane { + Lane::Direct => &mut self.direct, + Lane::Fanout => &mut self.fanout, + } + } +} + +#[derive(Debug)] +pub struct Scheduler { + limits: Limits, + seq: u64, + next_epoch: u64, + recipients: BTreeMap, + by_workspace: HashMap>, + direct: VecDeque, + fanout_retry: VecDeque, + queued: QueuedCounts, + cursors: HashMap, + cursor_order: VecDeque, + routes: HashMap, + route_ready: VecDeque, + running: Running, +} + +impl Scheduler { + pub(crate) fn new(limits: Limits) -> Self { + Self { + limits, + seq: 0, + next_epoch: 0, + recipients: BTreeMap::new(), + by_workspace: HashMap::new(), + direct: VecDeque::new(), + fanout_retry: VecDeque::new(), + queued: QueuedCounts::default(), + cursors: HashMap::new(), + cursor_order: VecDeque::new(), + routes: HashMap::new(), + route_ready: VecDeque::new(), + running: Running::default(), + } + } + + #[cfg(test)] + pub(crate) fn limits(&self) -> Limits { + self.limits + } + + pub(crate) fn current_seq(&self) -> u64 { + self.seq + } + + #[cfg(test)] + pub(crate) fn has_recipient(&self, sandbox_id: &str) -> bool { + self.recipients.contains_key(sandbox_id) + } + + /// Add or replace the local push recipient for a sandbox. + /// + /// A publication between bootstrap and registration may have passed this + /// sandbox's cursor position before it became visible, so it is rebuilt + /// directly. + pub(crate) fn register(&mut self, registration: Registration, now: Instant) { + let Registration { + sandbox_id, + session_id, + workspace, + providers, + captured_seq, + bootstrap_built, + } = registration; + self.remove_recipient(&sandbox_id); + self.next_epoch += 1; + let built_seq = if bootstrap_built { captured_seq } else { 0 }; + let built = || KeyState { + last_build_seq: built_seq, + rollback_seq: built_seq, + ..KeyState::default() + }; + self.by_workspace + .entry(workspace.clone()) + .or_default() + .insert(sandbox_id.clone()); + self.recipients.insert( + sandbox_id.clone(), + Recipient { + session_id, + epoch: self.next_epoch, + workspace, + providers, + providers_seq: captured_seq, + sandbox_config: built(), + provider_environment: built(), + }, + ); + if self.seq > captured_seq { + for component in ConfigComponents::ALL.selected() { + self.enqueue_key(&sandbox_id, component, Lane::Direct, now); + } + } + } + + pub(crate) fn unregister(&mut self, sandbox_id: &str, session_id: &str) { + if self + .recipients + .get(sandbox_id) + .is_some_and(|recipient| recipient.session_id == session_id) + { + self.remove_recipient(sandbox_id); + } + } + + /// Returns false when this gateway has no push recipient for the sandbox. + pub(crate) fn publish_sandbox( + &mut self, + sandbox_id: &str, + components: ConfigComponents, + now: Instant, + ) -> bool { + self.seq += 1; + if !self.recipients.contains_key(sandbox_id) { + return false; + } + for component in components.selected() { + self.enqueue_key(sandbox_id, component, Lane::Direct, now); + } + true + } + + pub(crate) fn publish_fanout( + &mut self, + scope: &FanoutScope, + components: ConfigComponents, + notify_peers: bool, + now: Instant, + ) { + self.seq += 1; + let seq = self.seq; + for component in components.selected() { + self.cursors.retain(|key, _| { + key.component != component || key.scope == *scope || !scope.includes(&key.scope) + }); + let key = FanoutKey { + scope: scope.clone(), + component, + }; + match self.cursors.entry(key.clone()) { + Entry::Occupied(mut entry) => { + let cursor = entry.get_mut(); + cursor.target_seq = seq; + cursor.lap_end.clone_from(&cursor.position); + cursor.wrapped = false; + } + Entry::Vacant(entry) => { + entry.insert(Cursor { + target_seq: seq, + position: None, + lap_end: None, + wrapped: false, + started: now, + }); + self.cursor_order.push_back(key); + } + } + } + let cursors = &self.cursors; + self.cursor_order.retain(|key| cursors.contains_key(key)); + if notify_peers { + self.route(RouteTarget::Peers(scope.clone()), components); + } + } + + pub(crate) fn route(&mut self, target: RouteTarget, components: ConfigComponents) { + let state = self.routes.entry(target.clone()).or_default(); + state.pending = state.pending.union(components); + if !state.queued && !state.in_flight { + state.queued = true; + self.route_ready.push_back(target); + } + } + + pub(crate) fn next_work(&mut self, now: Instant) -> Option { + if self.running.routes < self.limits.routes + && let Some(target) = self.route_ready.pop_front() + { + let state = self.routes.get_mut(&target).expect("ready route has state"); + state.queued = false; + state.in_flight = true; + self.running.routes += 1; + return Some(Work::Route(RouteTicket { + target, + components: std::mem::take(&mut state.pending), + })); + } + self.next_build(now) + } + + pub(crate) fn complete_build( + &mut self, + ticket: &BuildTicket, + outcome: BuildOutcome, + now: Instant, + ) { + *self.running.lane_mut(ticket.lane) -= 1; + let Some(recipient) = self.recipients.get_mut(&ticket.key.sandbox_id) else { + return; + }; + if recipient.epoch != ticket.epoch { + return; + } + let failed = outcome == BuildOutcome::Failed; + if let BuildOutcome::Built { + providers: Some(providers), + } = outcome + && ticket.seq >= recipient.providers_seq + { + recipient.providers = providers; + recipient.providers_seq = ticket.seq; + } + let state = recipient.key_mut(ticket.key.component); + state.in_flight = false; + let requeue = if failed { + state.last_build_seq = state.rollback_seq; + state.requeue.take().max(state.requeue_on_failure.take()) + } else { + state.requeue_on_failure = None; + state.requeue.take() + }; + if let Some(lane) = requeue { + self.enqueue_key(&ticket.key.sandbox_id, ticket.key.component, lane, now); + } + } + + pub(crate) fn complete_route(&mut self, ticket: &RouteTicket) { + self.running.routes -= 1; + let Some(state) = self.routes.get_mut(&ticket.target) else { + return; + }; + state.in_flight = false; + if state.pending.is_empty() { + self.routes.remove(&ticket.target); + } else { + state.queued = true; + self.route_ready.push_back(ticket.target.clone()); + } + } + + pub(crate) fn has_running_work(&self) -> bool { + self.running.builds() > 0 || self.running.routes > 0 + } + + pub(crate) fn record_gauges(&self) { + gauge!("openshell_supervisor_config_pending", "lane" => "direct") + .set(u32::try_from(self.queued.direct).unwrap_or(u32::MAX)); + gauge!("openshell_supervisor_config_pending", "lane" => "fanout") + .set(u32::try_from(self.queued.fanout).unwrap_or(u32::MAX)); + gauge!("openshell_supervisor_config_pending", "lane" => "route") + .set(u32::try_from(self.route_ready.len()).unwrap_or(u32::MAX)); + gauge!("openshell_supervisor_config_active_fanouts") + .set(u32::try_from(self.cursors.len()).unwrap_or(u32::MAX)); + } + + fn next_build(&mut self, now: Instant) -> Option { + if self.running.builds() >= self.limits.builds { + return None; + } + // Direct work leaves the last slot to waiting fanout work that has no + // build running yet. + let fanout_waiting = !self.fanout_retry.is_empty() || !self.cursors.is_empty(); + let keeps_fanout_floor = fanout_waiting + && self.running.fanout == 0 + && self.running.builds() + 1 == self.limits.builds; + if !keeps_fanout_floor && let Some(key) = self.pop_queued(Lane::Direct) { + return Some(self.dispatch(key, Lane::Direct, now)); + } + if self.running.fanout < self.limits.builds - self.limits.direct_reserve + && let Some(work) = self.next_fanout(now) + { + return Some(work); + } + // Fanout had nothing to start, so the floor is not needed. + if keeps_fanout_floor && let Some(key) = self.pop_queued(Lane::Direct) { + return Some(self.dispatch(key, Lane::Direct, now)); + } + None + } + + fn next_fanout(&mut self, now: Instant) -> Option { + if let Some(key) = self.pop_queued(Lane::Fanout) { + return Some(self.dispatch(key, Lane::Fanout, now)); + } + for _ in 0..self.cursor_order.len() { + let key = self.cursor_order.pop_front()?; + let Some(mut cursor) = self.cursors.remove(&key) else { + continue; + }; + if let Some(sandbox_id) = self.advance(&key, &mut cursor) { + let component = key.component; + self.cursors.insert(key.clone(), cursor); + self.cursor_order.push_back(key); + return Some(self.dispatch( + DeliveryKey { + sandbox_id, + component, + }, + Lane::Fanout, + now, + )); + } + histogram!( + "openshell_supervisor_config_fanout_pass_seconds", + "scope" => key.scope.metric_label(), + ) + .record(now.saturating_duration_since(cursor.started).as_secs_f64()); + } + None + } + + fn advance(&mut self, key: &FanoutKey, cursor: &mut Cursor) -> Option { + let component = key.component; + loop { + let Some(sandbox_id) = self.next_in_scope(&key.scope, cursor.position.as_deref()) + else { + if cursor.wrapped || cursor.lap_end.is_none() { + return None; + } + cursor.wrapped = true; + cursor.position = None; + continue; + }; + if cursor.wrapped + && cursor + .lap_end + .as_deref() + .is_some_and(|end| sandbox_id.as_str() > end) + { + return None; + } + cursor.position = Some(sandbox_id.clone()); + let recipient = self + .recipients + .get_mut(&sandbox_id) + .expect("scope index lists only recipients"); + // An in-flight build may be the one that installs a new attachment + // in the provider cache, so it cannot be filtered out yet. + if let FanoutScope::Provider { name, .. } = &key.scope + && !recipient.providers.contains(name) + && !recipient.key(component).in_flight + { + record_skip(component, "not_attached"); + continue; + } + let state = recipient.key_mut(component); + if state.in_flight { + if state.last_build_seq < cursor.target_seq { + state.requeue = state.requeue.max(Some(Lane::Fanout)); + } else { + state.requeue_on_failure = state.requeue_on_failure.max(Some(Lane::Fanout)); + } + record_skip(component, "in_flight"); + continue; + } + if state.last_build_seq >= cursor.target_seq { + record_skip(component, "current"); + continue; + } + if state.queued.is_some() { + record_skip(component, "queued"); + continue; + } + return Some(sandbox_id); + } + } + + fn next_in_scope(&self, scope: &FanoutScope, after: Option<&str>) -> Option { + let range = ( + after.map_or(Bound::Unbounded, Bound::Excluded), + Bound::Unbounded, + ); + match scope.workspace() { + None => self + .recipients + .range::(range) + .next() + .map(|(sandbox_id, _)| sandbox_id.clone()), + Some(workspace) => self + .by_workspace + .get(workspace)? + .range::(range) + .next() + .cloned(), + } + } + + fn pop_queued(&mut self, lane: Lane) -> Option { + loop { + let key = match lane { + Lane::Direct => self.direct.pop_front(), + Lane::Fanout => self.fanout_retry.pop_front(), + }?; + if self + .recipients + .get(&key.sandbox_id) + .is_some_and(|recipient| recipient.key(key.component).queued == Some(lane)) + { + return Some(key); + } + } + } + + fn dispatch(&mut self, key: DeliveryKey, lane: Lane, now: Instant) -> Work { + let seq = self.seq; + let recipient = self + .recipients + .get_mut(&key.sandbox_id) + .expect("dispatched key has a recipient"); + let state = recipient.key_mut(key.component); + self.queued.transition(state.queued, None); + state.queued = None; + state.in_flight = true; + state.rollback_seq = state.last_build_seq; + state.last_build_seq = seq; + if let Some(since) = state.waiting_since.take() { + histogram!( + "openshell_supervisor_config_dispatch_wait_seconds", + "lane" => lane.name(), + ) + .record(now.saturating_duration_since(since).as_secs_f64()); + } + *self.running.lane_mut(lane) += 1; + Work::Build(BuildTicket { + key, + lane, + session_id: recipient.session_id.clone(), + epoch: recipient.epoch, + seq, + }) + } + + fn enqueue_key( + &mut self, + sandbox_id: &str, + component: ConfigComponentKind, + lane: Lane, + now: Instant, + ) { + let Some(recipient) = self.recipients.get_mut(sandbox_id) else { + return; + }; + let state = recipient.key_mut(component); + if lane == Lane::Direct && state.waiting_since.is_none() { + state.waiting_since = Some(now); + } + if state.in_flight { + state.requeue = state.requeue.max(Some(lane)); + return; + } + if state.queued.is_some_and(|queued| queued >= lane) { + return; + } + self.queued.transition(state.queued, Some(lane)); + state.queued = Some(lane); + let key = DeliveryKey { + sandbox_id: sandbox_id.to_string(), + component, + }; + match lane { + Lane::Direct => self.direct.push_back(key), + Lane::Fanout => self.fanout_retry.push_back(key), + } + } + + fn remove_recipient(&mut self, sandbox_id: &str) { + let Some(recipient) = self.recipients.remove(sandbox_id) else { + return; + }; + self.queued + .transition(recipient.sandbox_config.queued, None); + self.queued + .transition(recipient.provider_environment.queued, None); + if let Some(sandboxes) = self.by_workspace.get_mut(&recipient.workspace) { + sandboxes.remove(sandbox_id); + if sandboxes.is_empty() { + self.by_workspace.remove(&recipient.workspace); + } + } + } +} + +fn record_skip(component: ConfigComponentKind, reason: &'static str) { + counter!( + "openshell_supervisor_config_build_skips_total", + "component" => component.name(), + "reason" => reason, + ) + .increment(1); +} + +#[cfg(test)] +mod tests { + use super::*; + + fn registration(sandbox_id: &str, workspace: &str, providers: &[&str]) -> Registration { + Registration { + sandbox_id: sandbox_id.to_string(), + session_id: format!("{sandbox_id}-session"), + workspace: workspace.to_string(), + providers: providers.iter().map(ToString::to_string).collect(), + captured_seq: 0, + bootstrap_built: true, + } + } + + fn scheduler(builds: usize) -> Scheduler { + Scheduler::new(Limits::new(builds, 2)) + } + + fn with_recipients(builds: usize, count: usize) -> Scheduler { + let mut scheduler = scheduler(builds); + for index in 0..count { + scheduler.register( + registration(&format!("sb-{index:04}"), "ws", &[]), + Instant::now(), + ); + } + scheduler + } + + fn build(work: Work) -> BuildTicket { + match work { + Work::Build(ticket) => ticket, + Work::Route(ticket) => panic!("expected a build, got {ticket:?}"), + } + } + + fn built() -> BuildOutcome { + BuildOutcome::Built { providers: None } + } + + /// Run all available work to completion, one ticket at a time. + fn drain(scheduler: &mut Scheduler) -> Vec { + let mut completed = Vec::new(); + while let Some(work) = scheduler.next_work(Instant::now()) { + match work { + Work::Build(ticket) => { + scheduler.complete_build(&ticket, built(), Instant::now()); + completed.push(ticket); + } + Work::Route(ticket) => scheduler.complete_route(&ticket), + } + } + completed + } + + fn sandbox_ids(tickets: &[BuildTicket]) -> Vec<&str> { + tickets + .iter() + .map(|ticket| ticket.key.sandbox_id.as_str()) + .collect() + } + + #[test] + fn sandbox_update_starts_while_a_fleet_fanout_holds_its_slots() { + let mut scheduler = with_recipients(4, 1000); + scheduler.publish_fanout( + &FanoutScope::AllConnected, + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + let mut fanout = Vec::new(); + while let Some(work) = scheduler.next_work(Instant::now()) { + fanout.push(build(work)); + } + assert_eq!(fanout.len(), 3, "fanout leaves the direct reserve free"); + assert!(fanout.iter().all(|ticket| ticket.lane == Lane::Fanout)); + + assert!(scheduler.publish_sandbox( + "sb-0999", + ConfigComponents::SANDBOX_CONFIG, + Instant::now() + )); + let direct = build(scheduler.next_work(Instant::now()).unwrap()); + assert_eq!(direct.key.sandbox_id, "sb-0999"); + assert_eq!(direct.lane, Lane::Direct); + } + + #[test] + fn sandbox_updates_never_freeze_a_fanout() { + let mut scheduler = scheduler(4); + for index in 0..100 { + scheduler.register( + registration(&format!("quiet-{index:03}"), "quiet", &[]), + Instant::now(), + ); + scheduler.register( + registration(&format!("busy-{index:03}"), "busy", &[]), + Instant::now(), + ); + } + scheduler.publish_fanout( + &FanoutScope::Workspace("quiet".into()), + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + for index in 0..100 { + scheduler.publish_sandbox( + &format!("busy-{index:03}"), + ConfigComponents::SANDBOX_CONFIG, + Instant::now(), + ); + } + let lanes = (0..4) + .map(|_| build(scheduler.next_work(Instant::now()).unwrap()).lane) + .collect::>(); + assert_eq!( + lanes, + [Lane::Direct, Lane::Direct, Lane::Direct, Lane::Fanout] + ); + } + + #[test] + fn repeated_updates_coalesce_without_rejection() { + let mut scheduler = with_recipients(2, 50); + for _ in 0..3 { + for index in 0..50 { + scheduler.publish_sandbox( + &format!("sb-{index:04}"), + ConfigComponents::ALL, + Instant::now(), + ); + } + } + let completed = drain(&mut scheduler); + assert_eq!(completed.len(), 100, "one build per sandbox component"); + let distinct = completed + .iter() + .map(|ticket| ticket.key.clone()) + .collect::>(); + assert_eq!(distinct.len(), 100); + } + + #[test] + fn publication_during_a_pass_costs_one_circle() { + const RECIPIENTS: usize = 100; + const BEFORE: usize = 30; + let mut scheduler = with_recipients(2, RECIPIENTS); + let scope = FanoutScope::AllConnected; + scheduler.publish_fanout( + &scope, + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + for _ in 0..BEFORE { + let ticket = build(scheduler.next_work(Instant::now()).unwrap()); + scheduler.complete_build(&ticket, built(), Instant::now()); + } + scheduler.publish_fanout( + &scope, + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + let second = scheduler.current_seq(); + let completed = drain(&mut scheduler); + + assert_eq!(completed.len(), RECIPIENTS); + assert_eq!( + completed.first().unwrap().key.sandbox_id, + format!("sb-{BEFORE:04}"), + "the pass continues from its position" + ); + assert!( + scheduler + .recipients + .values() + .all(|recipient| recipient.sandbox_config.last_build_seq >= second) + ); + assert!(scheduler.cursors.is_empty()); + } + + #[test] + fn wider_publication_replaces_narrower_passes() { + let mut scheduler = scheduler(4); + for (sandbox_id, workspace) in [("a", "ws-1"), ("b", "ws-1"), ("c", "ws-2")] { + scheduler.register(registration(sandbox_id, workspace, &["p"]), Instant::now()); + } + scheduler.publish_fanout( + &FanoutScope::Provider { + workspace: "ws-1".into(), + name: "p".into(), + }, + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + scheduler.publish_fanout( + &FanoutScope::Workspace("ws-1".into()), + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + scheduler.publish_fanout( + &FanoutScope::AllConnected, + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + assert_eq!(scheduler.cursors.len(), 1); + assert_eq!(sandbox_ids(&drain(&mut scheduler)), ["a", "b", "c"]); + } + + #[test] + fn overlapping_passes_build_each_sandbox_once() { + let mut scheduler = with_recipients(4, 20); + scheduler.publish_fanout( + &FanoutScope::AllConnected, + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + scheduler.publish_fanout( + &FanoutScope::Workspace("ws".into()), + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + assert_eq!(scheduler.cursors.len(), 2); + assert_eq!(drain(&mut scheduler).len(), 20); + } + + #[test] + fn fanout_skips_a_sandbox_rebuilt_after_its_publication() { + let mut scheduler = with_recipients(4, 3); + scheduler.publish_fanout( + &FanoutScope::AllConnected, + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + scheduler.publish_sandbox("sb-0002", ConfigComponents::SANDBOX_CONFIG, Instant::now()); + let completed = drain(&mut scheduler); + assert_eq!(sandbox_ids(&completed), ["sb-0002", "sb-0000", "sb-0001"]); + } + + #[test] + fn publication_during_a_build_rebuilds_once_in_its_lane() { + let mut scheduler = with_recipients(4, 1); + scheduler.publish_sandbox("sb-0000", ConfigComponents::SANDBOX_CONFIG, Instant::now()); + let first = build(scheduler.next_work(Instant::now()).unwrap()); + scheduler.publish_fanout( + &FanoutScope::AllConnected, + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + scheduler.publish_sandbox("sb-0000", ConfigComponents::SANDBOX_CONFIG, Instant::now()); + assert!( + scheduler.next_work(Instant::now()).is_none(), + "an in-flight key is not built twice concurrently" + ); + scheduler.complete_build(&first, built(), Instant::now()); + let second = build(scheduler.next_work(Instant::now()).unwrap()); + assert_eq!(second.lane, Lane::Direct); + scheduler.complete_build(&second, built(), Instant::now()); + assert!(drain(&mut scheduler).is_empty()); + } + + #[test] + fn failed_build_restores_its_sequence() { + let mut scheduler = with_recipients(4, 1); + scheduler.publish_sandbox("sb-0000", ConfigComponents::SANDBOX_CONFIG, Instant::now()); + let ticket = build(scheduler.next_work(Instant::now()).unwrap()); + scheduler.complete_build(&ticket, BuildOutcome::Failed, Instant::now()); + assert!(scheduler.next_work(Instant::now()).is_none()); + + scheduler.publish_fanout( + &FanoutScope::AllConnected, + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + assert_eq!(drain(&mut scheduler).len(), 1); + } + + #[test] + fn failed_build_retries_when_a_fanout_skipped_it_as_current() { + let mut scheduler = with_recipients(4, 1); + scheduler.publish_fanout( + &FanoutScope::AllConnected, + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + scheduler.publish_sandbox("sb-0000", ConfigComponents::SANDBOX_CONFIG, Instant::now()); + let ticket = build(scheduler.next_work(Instant::now()).unwrap()); + assert_eq!(ticket.lane, Lane::Direct); + assert!(scheduler.next_work(Instant::now()).is_none()); + scheduler.complete_build(&ticket, BuildOutcome::Failed, Instant::now()); + let retry = build(scheduler.next_work(Instant::now()).unwrap()); + assert_eq!(retry.lane, Lane::Fanout); + } + + #[test] + fn bootstrap_covers_registration_until_a_publication_races_it() { + let mut scheduler = scheduler(4); + let captured = scheduler.current_seq(); + scheduler.register( + Registration { + captured_seq: captured, + ..registration("fresh", "ws", &[]) + }, + Instant::now(), + ); + scheduler.publish_fanout( + &FanoutScope::Workspace("other".into()), + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + assert!(drain(&mut scheduler).is_empty()); + + let captured = scheduler.current_seq(); + scheduler.publish_sandbox( + "unrelated", + ConfigComponents::SANDBOX_CONFIG, + Instant::now(), + ); + scheduler.register( + Registration { + captured_seq: captured, + ..registration("raced", "ws", &[]) + }, + Instant::now(), + ); + let completed = drain(&mut scheduler); + assert_eq!(completed.len(), 2); + assert!(completed.iter().all(|ticket| ticket.lane == Lane::Direct)); + } + + #[test] + fn provider_pass_builds_only_attached_sandboxes() { + let mut scheduler = scheduler(4); + scheduler.register(registration("attached", "ws", &["github"]), Instant::now()); + scheduler.register(registration("other", "ws", &["gitlab"]), Instant::now()); + scheduler.register( + registration("elsewhere", "ws-2", &["github"]), + Instant::now(), + ); + scheduler.publish_fanout( + &FanoutScope::Provider { + workspace: "ws".into(), + name: "github".into(), + }, + ConfigComponents::ALL, + false, + Instant::now(), + ); + let completed = drain(&mut scheduler); + assert_eq!(completed.len(), 2); + assert!( + completed + .iter() + .all(|ticket| ticket.key.sandbox_id == "attached") + ); + } + + #[test] + fn provider_pass_includes_an_attachment_still_being_built() { + let mut scheduler = scheduler(4); + scheduler.register(registration("sb", "ws", &[]), Instant::now()); + scheduler.publish_sandbox("sb", ConfigComponents::SANDBOX_CONFIG, Instant::now()); + let attach = build(scheduler.next_work(Instant::now()).unwrap()); + scheduler.publish_fanout( + &FanoutScope::Provider { + workspace: "ws".into(), + name: "github".into(), + }, + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + assert!(scheduler.next_work(Instant::now()).is_none()); + scheduler.complete_build( + &attach, + BuildOutcome::Built { + providers: Some(HashSet::from(["github".to_string()])), + }, + Instant::now(), + ); + assert_eq!(sandbox_ids(&drain(&mut scheduler)), ["sb"]); + + scheduler.publish_fanout( + &FanoutScope::Provider { + workspace: "ws".into(), + name: "github".into(), + }, + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + assert_eq!( + sandbox_ids(&drain(&mut scheduler)), + ["sb"], + "the refreshed attachment cache keeps the sandbox in scope" + ); + } + + #[test] + fn late_build_does_not_roll_back_the_attachment_cache() { + let mut scheduler = scheduler(4); + scheduler.register(registration("sb", "ws", &[]), Instant::now()); + scheduler.publish_sandbox("sb", ConfigComponents::SANDBOX_CONFIG, Instant::now()); + let before_attach = build(scheduler.next_work(Instant::now()).unwrap()); + scheduler.publish_sandbox("sb", ConfigComponents::ALL, Instant::now()); + let after_attach = build(scheduler.next_work(Instant::now()).unwrap()); + assert_eq!( + after_attach.key.component, + ConfigComponentKind::ProviderEnvironment + ); + scheduler.complete_build( + &after_attach, + BuildOutcome::Built { + providers: Some(HashSet::from(["github".to_string()])), + }, + Instant::now(), + ); + scheduler.complete_build( + &before_attach, + BuildOutcome::Built { + providers: Some(HashSet::new()), + }, + Instant::now(), + ); + scheduler.publish_fanout( + &FanoutScope::Provider { + workspace: "ws".into(), + name: "github".into(), + }, + ConfigComponents::ALL, + false, + Instant::now(), + ); + let components = drain(&mut scheduler) + .into_iter() + .map(|ticket| ticket.key.component) + .collect::>(); + assert!( + components.contains(&ConfigComponentKind::ProviderEnvironment), + "the provider pass must reach the attached sandbox" + ); + } + + #[test] + fn fanout_floor_releases_when_fanout_has_nothing_to_start() { + let mut scheduler = scheduler(4); + for index in 0..4 { + scheduler.register( + registration(&format!("busy-{index}"), "busy", &[]), + Instant::now(), + ); + } + scheduler.publish_fanout( + &FanoutScope::Workspace("empty".into()), + ConfigComponents::SANDBOX_CONFIG, + false, + Instant::now(), + ); + for index in 0..4 { + scheduler.publish_sandbox( + &format!("busy-{index}"), + ConfigComponents::SANDBOX_CONFIG, + Instant::now(), + ); + } + let lanes = (0..4) + .map(|_| build(scheduler.next_work(Instant::now()).unwrap()).lane) + .collect::>(); + assert_eq!(lanes, [Lane::Direct; 4]); + } + + #[test] + fn remote_sandbox_updates_coalesce_into_one_route() { + let mut scheduler = scheduler(4); + assert!(!scheduler.publish_sandbox( + "remote", + ConfigComponents::SANDBOX_CONFIG, + Instant::now() + )); + scheduler.route( + RouteTarget::Sandbox("remote".into()), + ConfigComponents::SANDBOX_CONFIG, + ); + scheduler.route( + RouteTarget::Sandbox("remote".into()), + ConfigComponents { + sandbox_config: false, + provider_environment: true, + }, + ); + let Some(Work::Route(ticket)) = scheduler.next_work(Instant::now()) else { + panic!("expected a route"); + }; + assert_eq!(ticket.components, ConfigComponents::ALL); + assert!(scheduler.next_work(Instant::now()).is_none()); + + scheduler.route( + RouteTarget::Sandbox("remote".into()), + ConfigComponents::SANDBOX_CONFIG, + ); + assert!(scheduler.next_work(Instant::now()).is_none()); + scheduler.complete_route(&ticket); + let Some(Work::Route(again)) = scheduler.next_work(Instant::now()) else { + panic!("an update during routing routes again"); + }; + assert_eq!(again.components, ConfigComponents::SANDBOX_CONFIG); + scheduler.complete_route(&again); + assert!(scheduler.routes.is_empty()); + } + + #[test] + fn local_fanout_notifies_peers_once_per_publication() { + let mut scheduler = scheduler(4); + let scope = FanoutScope::Workspace("ws".into()); + scheduler.publish_fanout( + &scope, + ConfigComponents::SANDBOX_CONFIG, + true, + Instant::now(), + ); + scheduler.publish_fanout(&scope, ConfigComponents::ALL, true, Instant::now()); + let Some(Work::Route(ticket)) = scheduler.next_work(Instant::now()) else { + panic!("expected a peer hint"); + }; + assert_eq!(ticket.target, RouteTarget::Peers(scope)); + assert_eq!(ticket.components, ConfigComponents::ALL); + scheduler.complete_route(&ticket); + assert!(scheduler.next_work(Instant::now()).is_none()); + } + + #[test] + fn replaced_or_removed_sessions_drop_their_work() { + let mut scheduler = with_recipients(4, 1); + scheduler.publish_sandbox("sb-0000", ConfigComponents::SANDBOX_CONFIG, Instant::now()); + let stale = build(scheduler.next_work(Instant::now()).unwrap()); + scheduler.register(registration("sb-0000", "ws", &[]), Instant::now()); + scheduler.publish_sandbox("sb-0000", ConfigComponents::SANDBOX_CONFIG, Instant::now()); + scheduler.complete_build(&stale, built(), Instant::now()); + let current = build(scheduler.next_work(Instant::now()).unwrap()); + assert_ne!(current.epoch, stale.epoch); + + scheduler.publish_sandbox("sb-0000", ConfigComponents::SANDBOX_CONFIG, Instant::now()); + scheduler.unregister("sb-0000", "other-session"); + assert!(scheduler.recipients.contains_key("sb-0000")); + scheduler.unregister("sb-0000", "sb-0000-session"); + scheduler.complete_build(¤t, built(), Instant::now()); + assert!(scheduler.next_work(Instant::now()).is_none()); + assert!(!scheduler.has_running_work()); + assert!(scheduler.by_workspace.is_empty()); + assert_eq!((scheduler.queued.direct, scheduler.queued.fanout), (0, 0)); + } + + #[test] + fn limits_keep_both_lanes_schedulable() { + assert_eq!(Limits::new(0, 0), Limits::new(2, 1)); + let small = Limits::new(2, 1); + assert_eq!((small.builds, small.direct_reserve), (2, 1)); + let large = Limits::new(20, 8); + assert_eq!((large.builds, large.direct_reserve), (20, 5)); + } +} diff --git a/crates/openshell-server/src/config_delivery/session_slots.rs b/crates/openshell-server/src/config_delivery/session_slots.rs new file mode 100644 index 000000000..16ac2ab80 --- /dev/null +++ b/crates/openshell-server/src/config_delivery/session_slots.rs @@ -0,0 +1,178 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Latest-wins configuration slots for one supervisor session stream. + +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; + +use futures::task::AtomicWaker; +use openshell_core::proto::GatewayMessage; +use tokio::sync::mpsc; +use tonic::Status; + +use super::ConfigComponentKind; + +/// One unsent snapshot per component. A newer snapshot replaces an unsent one, +/// so a slow stream holds at most one message per component. +#[derive(Debug, Default)] +pub struct ConfigSlots { + pending: Mutex, + waker: AtomicWaker, +} + +#[derive(Debug, Default)] +struct PendingSlots { + next_order: u64, + sandbox_config: Option<(u64, GatewayMessage)>, + provider_environment: Option<(u64, GatewayMessage)>, +} + +impl ConfigSlots { + /// Returns true when an unsent snapshot was replaced. + pub(crate) fn replace(&self, component: ConfigComponentKind, message: GatewayMessage) -> bool { + let replaced = { + let mut pending = self.pending.lock().unwrap(); + let order = pending.next_order; + pending.next_order += 1; + let slot = match component { + ConfigComponentKind::SandboxConfig => &mut pending.sandbox_config, + ConfigComponentKind::ProviderEnvironment => &mut pending.provider_environment, + }; + slot.replace((order, message)).is_some() + }; + self.waker.wake(); + replaced + } + + fn take_oldest(&self) -> Option { + let mut pending = self.pending.lock().unwrap(); + let sandbox_first = match (&pending.sandbox_config, &pending.provider_environment) { + (Some((sandbox, _)), Some((provider, _))) => sandbox < provider, + (Some(_), None) => true, + (None, Some(_)) => false, + (None, None) => return None, + }; + let slot = if sandbox_first { + pending.sandbox_config.take() + } else { + pending.provider_environment.take() + }; + slot.map(|(_, message)| message) + } +} + +/// Session stream that sends queued control frames before configuration. +/// +/// `SessionAccepted` is queued on the control channel before the session can +/// receive configuration, so it always stays first. +pub struct SessionOutbound { + control: mpsc::Receiver, + config: Arc, +} + +impl SessionOutbound { + pub(crate) fn new(control: mpsc::Receiver, config: Arc) -> Self { + Self { control, config } + } +} + +impl tokio_stream::Stream for SessionOutbound { + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + match this.control.poll_recv(cx) { + Poll::Ready(Some(message)) => return Poll::Ready(Some(Ok(message))), + Poll::Ready(None) => return Poll::Ready(None), + Poll::Pending => {} + } + this.config.waker.register(cx.waker()); + this.config + .take_oldest() + .map_or(Poll::Pending, |message| Poll::Ready(Some(Ok(message)))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use openshell_core::proto::{ConfigUpdate, RelayOpen, gateway_message}; + use tokio_stream::StreamExt; + + fn config(update_id: &str) -> GatewayMessage { + GatewayMessage { + payload: Some(gateway_message::Payload::ConfigUpdate(ConfigUpdate { + update_id: update_id.into(), + ..Default::default() + })), + } + } + + fn relay_open() -> GatewayMessage { + GatewayMessage { + payload: Some(gateway_message::Payload::RelayOpen(RelayOpen::default())), + } + } + + fn update_id(message: GatewayMessage) -> String { + match message.payload { + Some(gateway_message::Payload::ConfigUpdate(update)) => update.update_id, + other => panic!("expected a configuration update, got {other:?}"), + } + } + + #[tokio::test] + async fn control_frames_go_before_queued_configuration() { + let (tx, rx) = mpsc::channel(4); + let slots = Arc::new(ConfigSlots::default()); + let mut outbound = SessionOutbound::new(rx, Arc::clone(&slots)); + assert!(!slots.replace(ConfigComponentKind::SandboxConfig, config("config"))); + tx.send(relay_open()).await.unwrap(); + + let first = outbound.next().await.unwrap().unwrap(); + assert!(matches!( + first.payload, + Some(gateway_message::Payload::RelayOpen(_)) + )); + assert_eq!(update_id(outbound.next().await.unwrap().unwrap()), "config"); + } + + #[tokio::test] + async fn newer_snapshot_replaces_an_unsent_one() { + let (_tx, rx) = mpsc::channel(4); + let slots = Arc::new(ConfigSlots::default()); + let mut outbound = SessionOutbound::new(rx, Arc::clone(&slots)); + assert!(!slots.replace(ConfigComponentKind::SandboxConfig, config("old"))); + assert!(!slots.replace(ConfigComponentKind::ProviderEnvironment, config("provider"))); + assert!(slots.replace(ConfigComponentKind::SandboxConfig, config("new"))); + + assert_eq!( + update_id(outbound.next().await.unwrap().unwrap()), + "provider" + ); + assert_eq!(update_id(outbound.next().await.unwrap().unwrap()), "new"); + } + + #[tokio::test] + async fn waiting_stream_wakes_for_configuration() { + let (_tx, rx) = mpsc::channel(4); + let slots = Arc::new(ConfigSlots::default()); + let mut outbound = SessionOutbound::new(rx, Arc::clone(&slots)); + let next = tokio::spawn(async move { outbound.next().await.unwrap().unwrap() }); + tokio::task::yield_now().await; + slots.replace(ConfigComponentKind::ProviderEnvironment, config("late")); + assert_eq!(update_id(next.await.unwrap()), "late"); + } + + #[tokio::test] + async fn stream_ends_with_the_control_channel() { + let (tx, rx) = mpsc::channel(4); + let slots = Arc::new(ConfigSlots::default()); + let mut outbound = SessionOutbound::new(rx, Arc::clone(&slots)); + slots.replace(ConfigComponentKind::SandboxConfig, config("discarded")); + drop(tx); + assert!(outbound.next().await.is_none()); + } +} diff --git a/crates/openshell-server/src/grpc/mod.rs b/crates/openshell-server/src/grpc/mod.rs index 3794ac51d..a9c7bd860 100644 --- a/crates/openshell-server/src/grpc/mod.rs +++ b/crates/openshell-server/src/grpc/mod.rs @@ -895,7 +895,7 @@ impl OpenShell for OpenShellService { &self, request: Request, ) -> Result, Status> { - crate::config_delivery::handle_peer_config_update_hint(&self.state, request).await + crate::config_delivery::handle_peer_config_update_hint(&self.state, request) } } diff --git a/crates/openshell-server/src/grpc/policy.rs b/crates/openshell-server/src/grpc/policy.rs index 2356770d9..48c9582f5 100644 --- a/crates/openshell-server/src/grpc/policy.rs +++ b/crates/openshell-server/src/grpc/policy.rs @@ -12746,14 +12746,8 @@ mod tests { sandbox.spec.as_mut().unwrap().policy = None; state.store.put_message(&sandbox).await.unwrap(); - let (tx, mut rx) = tokio::sync::mpsc::channel(4); - let (shutdown_tx, _shutdown_rx) = tokio::sync::oneshot::channel(); - state.supervisor_sessions.register( - "sb-published-policy".to_string(), - "session-1".to_string(), - tx, - shutdown_tx, - ); + let mut session = + crate::config_delivery::register_test_push_session(&state, &sandbox, "session-1"); let owner_index = crate::supervisor_owner::SupervisorOwnerIndex::new( Arc::clone(&state.store), crate::supervisor_owner::OWNER_TTL, @@ -12790,7 +12784,10 @@ mod tests { .expect("committed policy"); let snapshot = tokio::time::timeout(std::time::Duration::from_secs(2), async { loop { - let message = rx.recv().await.expect("configuration channel closed"); + let message = tokio_stream::StreamExt::next(&mut session.outbound) + .await + .expect("configuration stream closed") + .unwrap(); let Some(openshell_core::proto::gateway_message::Payload::ConfigUpdate(update)) = message.payload else { diff --git a/crates/openshell-server/src/grpc/provider.rs b/crates/openshell-server/src/grpc/provider.rs index 41437384d..7322c5731 100644 --- a/crates/openshell-server/src/grpc/provider.rs +++ b/crates/openshell-server/src/grpc/provider.rs @@ -2558,7 +2558,18 @@ async fn authorize_and_resolve_profile_workspace( } } -fn publish_provider_change(state: &Arc, workspace: &str) { +/// Publish a provider record change to the sandboxes that attach it. +fn publish_provider_change(state: &Arc, workspace: &str, provider_name: &str) { + crate::config_delivery::publish_provider_components( + state, + workspace, + provider_name, + crate::config_delivery::ConfigComponents::ALL, + ); +} + +/// A profile change affects every provider of its type in scope. +fn publish_provider_profile_change(state: &Arc, workspace: &str) { if workspace.is_empty() { crate::config_delivery::publish_all_connected( state, @@ -2642,7 +2653,7 @@ pub(super) async fn handle_create_provider( LifecycleOperation::Create, TelemetryOutcome::Success, ); - publish_provider_change(state, &workspace); + publish_provider_change(state, &workspace, provider.object_name()); Ok(Response::new(ProviderResponse { provider: Some(provider), ..Default::default() @@ -2912,7 +2923,7 @@ pub(super) async fn handle_import_provider_profiles( stored.profile.unwrap_or_default(), resource_version, )); - publish_provider_change(state, &workspace); + publish_provider_profile_change(state, &workspace); } Ok(Response::new(ImportProviderProfilesResponse { @@ -3048,7 +3059,7 @@ pub(super) async fn handle_update_provider_profiles( replay_facts.resource(&stored)?; let resource_version = stored_profile_resource_version(&stored); let profile = profile_response_payload(stored.profile.unwrap_or_default(), resource_version); - publish_provider_change(state, &workspace); + publish_provider_profile_change(state, &workspace); Ok(Response::new(UpdateProviderProfilesResponse { diagnostics: Vec::new(), @@ -3143,7 +3154,7 @@ pub(super) async fn handle_delete_provider_profile( .delete(StoredProviderProfile::object_type(), existing.object_id()) .await .map_err(|e| Status::internal(format!("delete provider profile failed: {e}")))?; - publish_provider_change(state, &workspace); + publish_provider_profile_change(state, &workspace); Ok(Response::new(DeleteProviderProfileResponse { outcome: openshell_core::proto::DeletionOutcome::Completed.into(), @@ -3996,7 +4007,7 @@ pub(super) async fn handle_update_provider( LifecycleOperation::Update, TelemetryOutcome::Success, ); - publish_provider_change(state, &workspace); + publish_provider_change(state, &workspace, provider.object_name()); Ok(Response::new(ProviderResponse { provider: Some(provider), target_receipts, @@ -4964,10 +4975,10 @@ pub(super) async fn handle_configure_provider_refresh( updated, ) .await; - publish_provider_change(state, &workspace); + publish_provider_change(state, &workspace, provider_name); result?; } else { - publish_provider_change(state, &workspace); + publish_provider_change(state, &workspace, provider_name); } replay_facts.refresh(&state_record)?; @@ -5013,7 +5024,7 @@ pub(super) async fn handle_rotate_provider_credential( credential_key, ) .await?; - publish_provider_change(state, &workspace); + publish_provider_change(state, &workspace, provider_name); replay_facts.refresh(&refresh_state)?; Ok(Response::new(RotateProviderCredentialResponse { @@ -5126,7 +5137,7 @@ pub(super) async fn handle_delete_provider_refresh( }) .map(|_| ())?; } - publish_provider_change(state, &workspace); + publish_provider_change(state, &workspace, provider_name); Ok(Response::new(DeleteProviderRefreshResponse { outcome: openshell_core::proto::DeletionOutcome::Completed.into(), @@ -5168,7 +5179,7 @@ pub(super) async fn handle_delete_provider( outcome, ); if deleted { - publish_provider_change(state, &workspace); + publish_provider_change(state, &workspace, &name); } Ok(Response::new(DeleteProviderResponse { outcome: super::deletion_outcome(deleted, req.allow_missing, "provider")?, diff --git a/crates/openshell-server/src/lib.rs b/crates/openshell-server/src/lib.rs index 371664feb..778b414ee 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -304,8 +304,8 @@ pub struct ServerState { /// Set once graceful gateway shutdown begins so stream handlers can /// distinguish expected transport closes from runtime failures. pub(crate) gateway_shutting_down: AtomicBool, - /// Per-sandbox scheduler for coalesced supervisor configuration delivery. - pub(crate) config_delivery_queue: config_delivery::ConfigDeliveryQueue, + /// Coalescing scheduler for supervisor configuration delivery. + pub(crate) config_delivery: config_delivery::ConfigDelivery, /// Routing boundary for local or remote supervisor configuration delivery. pub(crate) supervisor_config_router: Arc, @@ -435,8 +435,8 @@ impl ServerState { let supervisor_config_router: Arc = Arc::new( config_delivery::LocalSupervisorConfigRouter::new(Arc::clone(&supervisor_sessions)), ); - let config_delivery_queue = - config_delivery::ConfigDeliveryQueue::for_db_connections(store.max_connections()); + let config_delivery = + config_delivery::ConfigDelivery::for_db_connections(store.max_connections()); Self { config, store, @@ -451,7 +451,7 @@ impl ServerState { settings_mutex: tokio::sync::Mutex::new(()), supervisor_sessions, gateway_shutting_down: AtomicBool::new(false), - config_delivery_queue, + config_delivery, supervisor_config_router, replica_id, peer_endpoint, diff --git a/crates/openshell-server/src/provider_refresh.rs b/crates/openshell-server/src/provider_refresh.rs index 296795640..10e59a44a 100644 --- a/crates/openshell-server/src/provider_refresh.rs +++ b/crates/openshell-server/src/provider_refresh.rs @@ -1925,11 +1925,12 @@ pub fn spawn_refresh_worker(state: std::sync::Arc, interval: )) .await { - Ok(workspaces) => { - for workspace in workspaces { - crate::config_delivery::publish_workspace_components( + Ok(providers) => { + for (workspace, provider_name) in providers { + crate::config_delivery::publish_provider_components( &state, &workspace, + &provider_name, crate::config_delivery::ConfigComponents::ALL, ); } @@ -1942,12 +1943,13 @@ pub fn spawn_refresh_worker(state: std::sync::Arc, interval: }); } +/// Returns the `(workspace, provider)` pairs whose provider records changed. async fn run_refresh_worker_tick( store: &Store, credentials: Option<&crate::credentials::CredentialRuntime>, compute: Option<&crate::compute::ComputeRuntime>, -) -> Result, Status> { - let changed_workspaces = std::collections::HashSet::new(); +) -> Result, Status> { + let changed_providers = std::collections::HashSet::new(); let now_ms = current_time_ms(); let states = list_all_refresh_states(store).await?; let watched_count = states.len(); @@ -1967,7 +1969,7 @@ async fn run_refresh_worker_tick( .iter() .any(|state| refresh_state_has_work(state, now_ms)) { - return Ok(changed_workspaces); + return Ok(changed_providers); } let span = tracing::info_span!( "refresh", @@ -1998,8 +2000,8 @@ async fn refresh_states( compute: Option<&crate::compute::ComputeRuntime>, states: Vec, now_ms: i64, -) -> std::collections::HashSet { - let mut changed_workspaces = std::collections::HashSet::new(); +) -> std::collections::HashSet<(String, String)> { + let mut changed_providers = std::collections::HashSet::new(); for state in states { if state .metadata @@ -2030,7 +2032,7 @@ async fn refresh_states( "failed to finalize tombstoned provider refresh; retrying on the next sweep" ); } else { - changed_workspaces.insert(state.object_workspace().to_string()); + changed_providers.insert(changed_provider(&state)); } continue; } @@ -2115,10 +2117,17 @@ async fn refresh_states( "provider credential refresh failed" ); } else { - changed_workspaces.insert(state.object_workspace().to_string()); + changed_providers.insert(changed_provider(&state)); } } - changed_workspaces + changed_providers +} + +fn changed_provider(state: &StoredProviderCredentialRefreshState) -> (String, String) { + ( + state.object_workspace().to_string(), + state.provider_name.clone(), + ) } #[cfg(test)] @@ -3581,10 +3590,18 @@ mod tests { put_refresh_state(&store, &state).await.unwrap(); assert_eq!(credentials.stored_credential_count(), Some(1)); - Box::pin(run_refresh_worker_tick(&store, Some(&credentials), None)) + let changed = Box::pin(run_refresh_worker_tick(&store, Some(&credentials), None)) .await .unwrap(); + assert_eq!( + changed, + std::collections::HashSet::from([( + "default".to_string(), + "tombstoned-refresh".to_string() + )]), + "only the refreshed provider's attached sandboxes need an update" + ); assert!( get_refresh_state( &store, diff --git a/crates/openshell-server/src/storage_proto.rs b/crates/openshell-server/src/storage_proto.rs index eef3bc0fa..a0695cac6 100644 --- a/crates/openshell-server/src/storage_proto.rs +++ b/crates/openshell-server/src/storage_proto.rs @@ -130,7 +130,7 @@ mod tests { // Service authorization also extends both schemas additively. Legacy // payloads retain the safe Strip default. const PUBLIC_RPC_SCHEMA_SHA256: &str = - "73d5454909634839151b6d47fe4b62793d5426a783517541e91972766cf81aef"; + "95d4b5caeecb2fd482e2657ec264a5093f1180adee0f5a85b716bef3fdb48ad9"; const DURABLE_SCHEMA_SHA256: &str = "38165d9d76f49fcfe98a12f241e032838a2376c1d1a87ea2796fd33b9b1a3541"; const PUBLIC_DURABLE_OVERLAP_SHA256: &str = @@ -614,7 +614,7 @@ mod tests { overlap_hash.as_str(), ), ( - (321, 28), + (322, 28), (93, 21), (81, 21), PUBLIC_RPC_SCHEMA_SHA256, diff --git a/crates/openshell-server/src/supervisor_session.rs b/crates/openshell-server/src/supervisor_session.rs index 61d2acd5e..efcdb3d44 100644 --- a/crates/openshell-server/src/supervisor_session.rs +++ b/crates/openshell-server/src/supervisor_session.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::pin::Pin; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; @@ -33,12 +33,13 @@ use openshell_core::transport_errors::is_expected_transport_close_status; use crate::ServerState; use crate::auth::principal::Principal; use crate::config_delivery::{ - DeliveryDisposition, MAX_SUPERVISOR_CONFIG_MESSAGE_BYTES, SupervisorConfigMessage, + ConfigComponentKind, ConfigSlots, DeliveryDisposition, MAX_SUPERVISOR_CONFIG_MESSAGE_BYTES, + SessionOutbound, SupervisorConfigMessage, }; #[cfg(test)] use crate::config_delivery::{LocalSupervisorConfigRouter, SupervisorConfigRouter}; use crate::grpc::provider_readiness::ProviderReadinessEvidence; -use crate::persistence::ObjectId; +use crate::persistence::{ObjectId, ObjectWorkspace}; use crate::supervisor_owner::{OWNER_TTL, OwnerError, OwnerGuard, SupervisorOwnerIndex}; const HEARTBEAT_INTERVAL_SECS: u32 = 15; @@ -283,7 +284,8 @@ struct LiveSession { /// Uniquely identifies this session instance. Used by cleanup to avoid /// removing a session that has since been superseded by a reconnect. session_id: String, - config_push_capable: bool, + /// Present only for push-capable sessions on a gateway in push mode. + config_slots: Option>, tx: mpsc::Sender, config_sequences: ConfigSequences, /// Fires when this session is superseded by a reconnect so the old session @@ -422,16 +424,16 @@ impl SupervisorSessionRegistry { tx: mpsc::Sender, shutdown: oneshot::Sender<()>, ) -> bool { - self.register_with_config_push(sandbox_id, session_id, tx, shutdown, true) + self.register_with_config_slots(sandbox_id, session_id, tx, shutdown, None) } - pub fn register_with_config_push( + pub(crate) fn register_with_config_slots( &self, sandbox_id: String, session_id: String, tx: mpsc::Sender, shutdown: oneshot::Sender<()>, - config_push_capable: bool, + config_slots: Option>, ) -> bool { let mut sessions = self.sessions.lock().unwrap(); let previous = sessions.remove(&sandbox_id); @@ -440,7 +442,7 @@ impl SupervisorSessionRegistry { LiveSession { sandbox_id, session_id, - config_push_capable, + config_slots, tx, config_sequences: ConfigSequences::default(), shutdown, @@ -558,49 +560,36 @@ impl SupervisorSessionRegistry { true } - pub(crate) fn connected_sandbox_ids(&self) -> Vec { - self.sessions - .lock() - .unwrap() - .iter() - .filter(|(_, session)| session.config_push_capable) - .map(|(sandbox_id, _)| sandbox_id.clone()) - .collect() - } - - pub(crate) fn is_config_push_capable(&self, sandbox_id: &str) -> bool { - self.sessions - .lock() - .unwrap() - .get(sandbox_id) - .is_some_and(|session| session.config_push_capable) - } - + /// Hand a snapshot to the session it was built for. A replacement session + /// received its own bootstrap and must not receive an older build. pub(crate) fn deliver_config( &self, sandbox_id: &str, + session_id: &str, message: SupervisorConfigMessage, ) -> DeliveryDisposition { - let component_name = message.component_name(); + let component = message.component(); let mut sessions = self.sessions.lock().unwrap(); - let Some(session) = sessions.get_mut(sandbox_id) else { + let Some(session) = sessions + .get_mut(sandbox_id) + .filter(|session| session.session_id == session_id) + else { return DeliveryDisposition::NoActiveSession; }; - if !session.config_push_capable { + let Some(slots) = session.config_slots.clone() else { return DeliveryDisposition::UnsupportedSession; - } - let sequence = match &message { - SupervisorConfigMessage::SandboxConfig(_) => { - &mut session.config_sequences.sandbox_config - } - SupervisorConfigMessage::ProviderEnvironment(_) => { + }; + let sequence = match component { + ConfigComponentKind::SandboxConfig => &mut session.config_sequences.sandbox_config, + ConfigComponentKind::ProviderEnvironment => { &mut session.config_sequences.provider_environment } }; + // Replacing an unsent snapshot leaves a gap in the sequence. *sequence = sequence.saturating_add(1); let component_sequence = *sequence; - let component = match message { + let snapshot = match message { SupervisorConfigMessage::SandboxConfig(snapshot) => { config_update::Component::SandboxConfig(*snapshot) } @@ -612,25 +601,19 @@ impl SupervisorSessionRegistry { payload: Some(gateway_message::Payload::ConfigUpdate(ConfigUpdate { update_id: Uuid::new_v4().to_string(), component_sequence, - component: Some(component), + component: Some(snapshot), })), }; if gateway_message.encoded_len() > MAX_SUPERVISOR_CONFIG_MESSAGE_BYTES { return DeliveryDisposition::PayloadTooLarge; } + drop(sessions); - match session.tx.try_send(gateway_message) { - Ok(()) => DeliveryDisposition::Enqueued, - Err(mpsc::error::TrySendError::Full(_)) => { - warn!( - sandbox_id = %sandbox_id, - component = component_name, - "supervisor configuration queue is full" - ); - DeliveryDisposition::QueueFull - } - Err(mpsc::error::TrySendError::Closed(_)) => DeliveryDisposition::SessionClosed, + if slots.replace(component, gateway_message) { + DeliveryDisposition::Replaced + } else { + DeliveryDisposition::Queued } } @@ -1989,12 +1972,24 @@ pub async fn handle_connect_supervisor( if let Some(principal) = principal.as_ref() { crate::auth::guard::ensure_sandbox_principal_scope(principal, &sandbox_id)?; } + // Captured before reading any bootstrap input, including the sandbox + // record, so registration detects publications the bootstrap may miss. + let captured_seq = (state.config.config_delivery_mode + == openshell_core::config::ConfigDeliveryMode::Push + && hello.protocol_revision == SUPERVISOR_PROTOCOL_REVISION) + .then(|| state.config_delivery.current_seq()); let sandbox = require_persisted_sandbox(&state.store, &sandbox_id).await?; - let bootstrap = if state.config.config_delivery_mode - == openshell_core::config::ConfigDeliveryMode::Poll - || hello.protocol_revision == LEGACY_SUPERVISOR_PROTOCOL_REVISION - { + let config_push = captured_seq.map(|captured_seq| ConfigPushSetup { + captured_seq, + workspace: sandbox.object_workspace().to_string(), + providers: sandbox + .spec + .as_ref() + .map(|spec| spec.providers.iter().cloned().collect()) + .unwrap_or_default(), + }); + let bootstrap = if config_push.is_none() { None } else { match crate::config_delivery::build_config_bootstrap(state, &sandbox).await { @@ -2034,6 +2029,7 @@ pub async fn handle_connect_supervisor( inbound, hello, bootstrap, + config_push, provider_readiness, session_lifetime, )) @@ -2041,11 +2037,18 @@ pub async fn handle_connect_supervisor( .map_err(|error| Status::internal(format!("supervisor session setup failed: {error}")))? } +struct ConfigPushSetup { + captured_seq: u64, + workspace: String, + providers: HashSet, +} + async fn establish_supervisor_session( state: Arc, mut inbound: tonic::Streaming, hello: SupervisorHello, bootstrap: Option, + config_push: Option, provider_readiness: ProviderReadinessEvidence, session_lifetime: OwnedRwLockReadGuard<()>, ) -> Result< @@ -2109,6 +2112,16 @@ async fn establish_supervisor_session( }; accepted_payload.bootstrap = None; } + let bootstrap_built = matches!( + &accepted.payload, + Some(gateway_message::Payload::SessionAccepted(SessionAccepted { + bootstrap: Some(_), + .. + })) + ); + let config_slots = config_push + .as_ref() + .map(|_| Arc::new(ConfigSlots::default())); if tx.send(accepted).await.is_err() { if let Err(err) = owner_index.release_if_current(&owner_guard).await { warn!(sandbox_id = %sandbox_id, session_id = %session_id, error = %err, "supervisor session: failed to release owner after accept queue failure"); @@ -2116,12 +2129,12 @@ async fn establish_supervisor_session( return Err(Status::internal("failed to send session accepted")); } - let superseded = state.supervisor_sessions.register_with_config_push( + let superseded = state.supervisor_sessions.register_with_config_slots( sandbox_id.clone(), session_id.clone(), tx.clone(), shutdown_tx, - hello.protocol_revision == SUPERVISOR_PROTOCOL_REVISION, + config_slots.clone(), ); if superseded { info!( @@ -2210,6 +2223,20 @@ async fn establish_supervisor_session( .replay_pending_relays(&sandbox_id, &session_id, &tx) .await; + if let Some(setup) = config_push { + crate::config_delivery::register_session( + &state, + crate::config_delivery::Registration { + sandbox_id: sandbox_id.clone(), + session_id: session_id.clone(), + workspace: setup.workspace, + providers: setup.providers, + captured_seq: setup.captured_seq, + bootstrap_built, + }, + ); + } + // Step 4: Spawn the session loop that reads inbound messages. let state_clone = Arc::clone(&state); let sandbox_id_clone = sandbox_id.clone(); @@ -2229,6 +2256,8 @@ async fn establish_supervisor_session( let terminal_finalized = state_clone .supervisor_sessions .remove_if_current(&sandbox_id_clone, &session_id); + // Every removal path ends this task, including supersede and disconnect. + crate::config_delivery::unregister_session(&state_clone, &sandbox_id_clone, &session_id); // Release only this exact ownership record. A newer supervisor session // may already have published replacement ownership on another replica. let owner_index = SupervisorOwnerIndex::new(state_clone.store.clone(), OWNER_TTL); @@ -2264,10 +2293,12 @@ async fn establish_supervisor_session( }); // Return the outbound stream. - let stream = ReceiverStream::new(rx); let stream: Pin< Box> + Send + 'static>, - > = Box::pin(tokio_stream::StreamExt::map(stream, Ok)); + > = match config_slots { + Some(slots) => Box::pin(SessionOutbound::new(rx, slots)), + None => Box::pin(tokio_stream::StreamExt::map(ReceiverStream::new(rx), Ok)), + }; Ok(Response::new(stream)) } @@ -2699,7 +2730,7 @@ mod tests { assert_eq!(status.code(), tonic::Code::FailedPrecondition); assert!(status.message().contains("revision mismatch")); - assert!(state.supervisor_sessions.connected_sandbox_ids().is_empty()); + assert!(!state.supervisor_sessions.has_session("sb-future")); } #[test] @@ -2710,6 +2741,36 @@ mod tests { assert!(error.message().contains("revision mismatch")); } + fn register_push_session( + registry: &SupervisorSessionRegistry, + sandbox_id: &str, + session_id: &str, + ) -> (mpsc::Sender, SessionOutbound) { + let (tx, rx) = mpsc::channel(4); + let slots = Arc::new(ConfigSlots::default()); + registry.register_with_config_slots( + sandbox_id.into(), + session_id.into(), + tx.clone(), + make_shutdown(), + Some(Arc::clone(&slots)), + ); + (tx, SessionOutbound::new(rx, slots)) + } + + fn component_sequence(message: GatewayMessage) -> (bool, u64) { + match message.payload { + Some(gateway_message::Payload::ConfigUpdate(update)) => ( + matches!( + update.component, + Some(config_update::Component::SandboxConfig(_)) + ), + update.component_sequence, + ), + other => panic!("expected config update, got {other:?}"), + } + } + #[tokio::test] async fn config_router_reports_missing_session() { let router = LocalSupervisorConfigRouter::new(Arc::new(SupervisorSessionRegistry::new())); @@ -2717,6 +2778,7 @@ mod tests { router .deliver( "missing", + "session", SupervisorConfigMessage::SandboxConfig(Box::default()), ) .await, @@ -2725,136 +2787,102 @@ mod tests { } #[tokio::test] - async fn config_router_assigns_sequences_per_component() { + async fn config_router_keeps_the_latest_unsent_snapshot_per_component() { + use tokio_stream::StreamExt as _; + let registry = Arc::new(SupervisorSessionRegistry::new()); let router = LocalSupervisorConfigRouter::new(Arc::clone(®istry)); - let (tx, mut rx) = mpsc::channel(4); - let (shutdown_tx, _shutdown_rx) = oneshot::channel(); - registry.register("sb-1".into(), "session-1".into(), tx, shutdown_tx); + let (_tx, mut outbound) = register_push_session(®istry, "sb-1", "session-1"); + for expected in [DeliveryDisposition::Queued, DeliveryDisposition::Replaced] { + assert_eq!( + router + .deliver( + "sb-1", + "session-1", + SupervisorConfigMessage::SandboxConfig(Box::default()), + ) + .await, + expected + ); + } assert_eq!( router .deliver( "sb-1", - SupervisorConfigMessage::SandboxConfig(Box::default()), - ) - .await, - DeliveryDisposition::Enqueued - ); - assert_eq!( - router - .deliver( - "sb-1", - SupervisorConfigMessage::SandboxConfig(Box::default()), - ) - .await, - DeliveryDisposition::Enqueued - ); - assert_eq!( - router - .deliver( - "sb-1", + "session-1", SupervisorConfigMessage::ProviderEnvironment( ProviderEnvironmentSnapshot::default(), ), ) .await, - DeliveryDisposition::Enqueued + DeliveryDisposition::Queued ); - let first = rx.recv().await.expect("first config update"); - let second = rx.recv().await.expect("second config update"); - let third = rx.recv().await.expect("provider config update"); - let sequence = |message: GatewayMessage| match message.payload { - Some(gateway_message::Payload::ConfigUpdate(update)) => update.component_sequence, - other => panic!("expected config update, got {other:?}"), - }; - assert_eq!(sequence(first), 1); - assert_eq!(sequence(second), 2); - assert_eq!(sequence(third), 1); + let first = outbound.next().await.unwrap().unwrap(); + let second = outbound.next().await.unwrap().unwrap(); + assert_eq!(component_sequence(first), (true, 2)); + assert_eq!(component_sequence(second), (false, 1)); } #[tokio::test] - async fn config_router_uses_replacement_session() { + async fn config_router_never_crosses_into_a_replacement_session() { + use tokio_stream::StreamExt as _; + let registry = Arc::new(SupervisorSessionRegistry::new()); let router = LocalSupervisorConfigRouter::new(Arc::clone(®istry)); - let (old_tx, mut old_rx) = mpsc::channel(2); - let (old_shutdown_tx, _old_shutdown_rx) = oneshot::channel(); - registry.register("sb-1".into(), "old-session".into(), old_tx, old_shutdown_tx); + let (_old_tx, _old_outbound) = register_push_session(®istry, "sb-1", "old-session"); + let (_new_tx, mut new_outbound) = register_push_session(®istry, "sb-1", "new-session"); assert_eq!( router .deliver( "sb-1", + "old-session", SupervisorConfigMessage::SandboxConfig(Box::default()), ) .await, - DeliveryDisposition::Enqueued + DeliveryDisposition::NoActiveSession ); - let old_update = old_rx.recv().await.expect("old-session config update"); - let Some(gateway_message::Payload::ConfigUpdate(old_update)) = old_update.payload else { - panic!("expected config update"); - }; - assert_eq!(old_update.component_sequence, 1); - - let (new_tx, mut new_rx) = mpsc::channel(1); - let (new_shutdown_tx, _new_shutdown_rx) = oneshot::channel(); - assert!(registry.register("sb-1".into(), "new-session".into(), new_tx, new_shutdown_tx,)); - assert_eq!( router .deliver( "sb-1", + "new-session", SupervisorConfigMessage::SandboxConfig(Box::default()), ) .await, - DeliveryDisposition::Enqueued + DeliveryDisposition::Queued ); - assert!(old_rx.try_recv().is_err()); - let new_update = new_rx.try_recv().expect("new-session config update"); - let Some(gateway_message::Payload::ConfigUpdate(new_update)) = new_update.payload else { - panic!("expected config update"); - }; - assert_eq!(new_update.component_sequence, 1); + let update = new_outbound.next().await.unwrap().unwrap(); + assert_eq!(component_sequence(update), (true, 1)); } #[tokio::test] - async fn config_router_reports_full_and_closed_queues() { + async fn config_router_skips_sessions_without_push() { let registry = Arc::new(SupervisorSessionRegistry::new()); let router = LocalSupervisorConfigRouter::new(Arc::clone(®istry)); - let (tx, rx) = mpsc::channel(1); - tx.try_send(GatewayMessage::default()).unwrap(); - let (shutdown_tx, _shutdown_rx) = oneshot::channel(); - registry.register("sb-1".into(), "session-1".into(), tx, shutdown_tx); + let (tx, _rx) = mpsc::channel(1); + registry.register("sb-1".into(), "session-1".into(), tx, make_shutdown()); assert_eq!( router .deliver( "sb-1", + "session-1", SupervisorConfigMessage::SandboxConfig(Box::default()), ) .await, - DeliveryDisposition::QueueFull - ); - - drop(rx); - assert_eq!( - router - .deliver( - "sb-1", - SupervisorConfigMessage::SandboxConfig(Box::default()), - ) - .await, - DeliveryDisposition::SessionClosed + DeliveryDisposition::UnsupportedSession ); } #[tokio::test] async fn config_router_rejects_oversized_messages() { + use tokio_stream::StreamExt as _; + let registry = Arc::new(SupervisorSessionRegistry::new()); let router = LocalSupervisorConfigRouter::new(Arc::clone(®istry)); - let (tx, mut rx) = mpsc::channel(1); - let (shutdown_tx, _shutdown_rx) = oneshot::channel(); - registry.register("sb-1".into(), "session-1".into(), tx, shutdown_tx); + let (tx, mut outbound) = register_push_session(®istry, "sb-1", "session-1"); let snapshot = ProviderEnvironmentSnapshot { values: vec![ProviderEnvironmentValue { @@ -2868,12 +2896,15 @@ mod tests { router .deliver( "sb-1", + "session-1", SupervisorConfigMessage::ProviderEnvironment(snapshot), ) .await, DeliveryDisposition::PayloadTooLarge ); - assert!(rx.try_recv().is_err()); + drop(tx); + registry.remove_if_current("sb-1", "session-1"); + assert!(outbound.next().await.is_none()); } use tokio::io::{AsyncReadExt, AsyncWriteExt}; diff --git a/proto/openshell.proto b/proto/openshell.proto index 4e76b4afd..c16ca7704 100644 --- a/proto/openshell.proto +++ b/proto/openshell.proto @@ -3225,11 +3225,18 @@ message PeerConfigSandboxTarget { string session_id = 2; } +// Sandboxes in a workspace that attach the named provider. +message PeerConfigProviderTarget { + string workspace = 1; + string name = 2; +} + message PeerConfigUpdateHintRequest { oneof scope { PeerConfigSandboxTarget sandbox = 1; string workspace = 2; bool all_connected = 3; + PeerConfigProviderTarget provider = 6; } bool sandbox_config = 4; bool provider_environment = 5; @@ -3238,8 +3245,6 @@ message PeerConfigUpdateHintRequest { message PeerConfigUpdateHintResponse { // The targeted session moved before the receiving replica accepted the hint. bool stale_owner = 1; - // The bounded local delivery or fanout queue could not accept the hint. - bool queue_full = 2; } // Supervisor reports the result of a relay open request. diff --git a/sdk/go/proto/openshellv1/openshell.pb.go b/sdk/go/proto/openshellv1/openshell.pb.go index 1167707d5..ed316688c 100644 --- a/sdk/go/proto/openshellv1/openshell.pb.go +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -15052,6 +15052,59 @@ func (x *PeerConfigSandboxTarget) GetSessionId() string { return "" } +// Sandboxes in a workspace that attach the named provider. +type PeerConfigProviderTarget struct { + state protoimpl.MessageState `protogen:"open.v1"` + Workspace string `protobuf:"bytes,1,opt,name=workspace,proto3" json:"workspace,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PeerConfigProviderTarget) Reset() { + *x = PeerConfigProviderTarget{} + mi := &file_openshell_proto_msgTypes[191] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PeerConfigProviderTarget) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PeerConfigProviderTarget) ProtoMessage() {} + +func (x *PeerConfigProviderTarget) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[191] + 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 PeerConfigProviderTarget.ProtoReflect.Descriptor instead. +func (*PeerConfigProviderTarget) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{191} +} + +func (x *PeerConfigProviderTarget) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +func (x *PeerConfigProviderTarget) GetName() string { + if x != nil { + return x.Name + } + return "" +} + type PeerConfigUpdateHintRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Types that are valid to be assigned to Scope: @@ -15059,6 +15112,7 @@ type PeerConfigUpdateHintRequest struct { // *PeerConfigUpdateHintRequest_Sandbox // *PeerConfigUpdateHintRequest_Workspace // *PeerConfigUpdateHintRequest_AllConnected + // *PeerConfigUpdateHintRequest_Provider Scope isPeerConfigUpdateHintRequest_Scope `protobuf_oneof:"scope"` SandboxConfig bool `protobuf:"varint,4,opt,name=sandbox_config,json=sandboxConfig,proto3" json:"sandbox_config,omitempty"` ProviderEnvironment bool `protobuf:"varint,5,opt,name=provider_environment,json=providerEnvironment,proto3" json:"provider_environment,omitempty"` @@ -15068,7 +15122,7 @@ type PeerConfigUpdateHintRequest struct { func (x *PeerConfigUpdateHintRequest) Reset() { *x = PeerConfigUpdateHintRequest{} - mi := &file_openshell_proto_msgTypes[191] + mi := &file_openshell_proto_msgTypes[192] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15080,7 +15134,7 @@ func (x *PeerConfigUpdateHintRequest) String() string { func (*PeerConfigUpdateHintRequest) ProtoMessage() {} func (x *PeerConfigUpdateHintRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[191] + mi := &file_openshell_proto_msgTypes[192] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15093,7 +15147,7 @@ func (x *PeerConfigUpdateHintRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerConfigUpdateHintRequest.ProtoReflect.Descriptor instead. func (*PeerConfigUpdateHintRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{191} + return file_openshell_proto_rawDescGZIP(), []int{192} } func (x *PeerConfigUpdateHintRequest) GetScope() isPeerConfigUpdateHintRequest_Scope { @@ -15130,6 +15184,15 @@ func (x *PeerConfigUpdateHintRequest) GetAllConnected() bool { return false } +func (x *PeerConfigUpdateHintRequest) GetProvider() *PeerConfigProviderTarget { + if x != nil { + if x, ok := x.Scope.(*PeerConfigUpdateHintRequest_Provider); ok { + return x.Provider + } + } + return nil +} + func (x *PeerConfigUpdateHintRequest) GetSandboxConfig() bool { if x != nil { return x.SandboxConfig @@ -15160,25 +15223,29 @@ type PeerConfigUpdateHintRequest_AllConnected struct { AllConnected bool `protobuf:"varint,3,opt,name=all_connected,json=allConnected,proto3,oneof"` } +type PeerConfigUpdateHintRequest_Provider struct { + Provider *PeerConfigProviderTarget `protobuf:"bytes,6,opt,name=provider,proto3,oneof"` +} + func (*PeerConfigUpdateHintRequest_Sandbox) isPeerConfigUpdateHintRequest_Scope() {} func (*PeerConfigUpdateHintRequest_Workspace) isPeerConfigUpdateHintRequest_Scope() {} func (*PeerConfigUpdateHintRequest_AllConnected) isPeerConfigUpdateHintRequest_Scope() {} +func (*PeerConfigUpdateHintRequest_Provider) isPeerConfigUpdateHintRequest_Scope() {} + type PeerConfigUpdateHintResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // The targeted session moved before the receiving replica accepted the hint. - StaleOwner bool `protobuf:"varint,1,opt,name=stale_owner,json=staleOwner,proto3" json:"stale_owner,omitempty"` - // The bounded local delivery or fanout queue could not accept the hint. - QueueFull bool `protobuf:"varint,2,opt,name=queue_full,json=queueFull,proto3" json:"queue_full,omitempty"` + StaleOwner bool `protobuf:"varint,1,opt,name=stale_owner,json=staleOwner,proto3" json:"stale_owner,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *PeerConfigUpdateHintResponse) Reset() { *x = PeerConfigUpdateHintResponse{} - mi := &file_openshell_proto_msgTypes[192] + mi := &file_openshell_proto_msgTypes[193] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15190,7 +15257,7 @@ func (x *PeerConfigUpdateHintResponse) String() string { func (*PeerConfigUpdateHintResponse) ProtoMessage() {} func (x *PeerConfigUpdateHintResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[192] + mi := &file_openshell_proto_msgTypes[193] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15203,7 +15270,7 @@ func (x *PeerConfigUpdateHintResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerConfigUpdateHintResponse.ProtoReflect.Descriptor instead. func (*PeerConfigUpdateHintResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{192} + return file_openshell_proto_rawDescGZIP(), []int{193} } func (x *PeerConfigUpdateHintResponse) GetStaleOwner() bool { @@ -15213,13 +15280,6 @@ func (x *PeerConfigUpdateHintResponse) GetStaleOwner() bool { return false } -func (x *PeerConfigUpdateHintResponse) GetQueueFull() bool { - if x != nil { - return x.QueueFull - } - return false -} - // Supervisor reports the result of a relay open request. type RelayOpenResult struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -15235,7 +15295,7 @@ type RelayOpenResult struct { func (x *RelayOpenResult) Reset() { *x = RelayOpenResult{} - mi := &file_openshell_proto_msgTypes[193] + mi := &file_openshell_proto_msgTypes[194] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15247,7 +15307,7 @@ func (x *RelayOpenResult) String() string { func (*RelayOpenResult) ProtoMessage() {} func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[193] + mi := &file_openshell_proto_msgTypes[194] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15260,7 +15320,7 @@ func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpenResult.ProtoReflect.Descriptor instead. func (*RelayOpenResult) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{193} + return file_openshell_proto_rawDescGZIP(), []int{194} } func (x *RelayOpenResult) GetChannelId() string { @@ -15297,7 +15357,7 @@ type RelayClose struct { func (x *RelayClose) Reset() { *x = RelayClose{} - mi := &file_openshell_proto_msgTypes[194] + mi := &file_openshell_proto_msgTypes[195] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15309,7 +15369,7 @@ func (x *RelayClose) String() string { func (*RelayClose) ProtoMessage() {} func (x *RelayClose) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[194] + mi := &file_openshell_proto_msgTypes[195] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15322,7 +15382,7 @@ func (x *RelayClose) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayClose.ProtoReflect.Descriptor instead. func (*RelayClose) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{194} + return file_openshell_proto_rawDescGZIP(), []int{195} } func (x *RelayClose) GetChannelId() string { @@ -15356,7 +15416,7 @@ type L7RequestSample struct { func (x *L7RequestSample) Reset() { *x = L7RequestSample{} - mi := &file_openshell_proto_msgTypes[195] + mi := &file_openshell_proto_msgTypes[196] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15368,7 +15428,7 @@ func (x *L7RequestSample) String() string { func (*L7RequestSample) ProtoMessage() {} func (x *L7RequestSample) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[195] + mi := &file_openshell_proto_msgTypes[196] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15381,7 +15441,7 @@ func (x *L7RequestSample) ProtoReflect() protoreflect.Message { // Deprecated: Use L7RequestSample.ProtoReflect.Descriptor instead. func (*L7RequestSample) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{195} + return file_openshell_proto_rawDescGZIP(), []int{196} } func (x *L7RequestSample) GetMethod() string { @@ -15455,7 +15515,7 @@ type DenialSummary struct { func (x *DenialSummary) Reset() { *x = DenialSummary{} - mi := &file_openshell_proto_msgTypes[196] + mi := &file_openshell_proto_msgTypes[197] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15467,7 +15527,7 @@ func (x *DenialSummary) String() string { func (*DenialSummary) ProtoMessage() {} func (x *DenialSummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[196] + mi := &file_openshell_proto_msgTypes[197] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15480,7 +15540,7 @@ func (x *DenialSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialSummary.ProtoReflect.Descriptor instead. func (*DenialSummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{196} + return file_openshell_proto_rawDescGZIP(), []int{197} } func (x *DenialSummary) GetSandboxId() string { @@ -15615,7 +15675,7 @@ type DenialGroupCount struct { func (x *DenialGroupCount) Reset() { *x = DenialGroupCount{} - mi := &file_openshell_proto_msgTypes[197] + mi := &file_openshell_proto_msgTypes[198] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15627,7 +15687,7 @@ func (x *DenialGroupCount) String() string { func (*DenialGroupCount) ProtoMessage() {} func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[197] + mi := &file_openshell_proto_msgTypes[198] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15640,7 +15700,7 @@ func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialGroupCount.ProtoReflect.Descriptor instead. func (*DenialGroupCount) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{197} + return file_openshell_proto_rawDescGZIP(), []int{198} } func (x *DenialGroupCount) GetDenyGroup() string { @@ -15673,7 +15733,7 @@ type NetworkActivitySummary struct { func (x *NetworkActivitySummary) Reset() { *x = NetworkActivitySummary{} - mi := &file_openshell_proto_msgTypes[198] + mi := &file_openshell_proto_msgTypes[199] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15685,7 +15745,7 @@ func (x *NetworkActivitySummary) String() string { func (*NetworkActivitySummary) ProtoMessage() {} func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[198] + mi := &file_openshell_proto_msgTypes[199] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15698,7 +15758,7 @@ func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkActivitySummary.ProtoReflect.Descriptor instead. func (*NetworkActivitySummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{198} + return file_openshell_proto_rawDescGZIP(), []int{199} } func (x *NetworkActivitySummary) GetNetworkActivityCount() uint32 { @@ -15786,7 +15846,7 @@ type PolicyChunk struct { func (x *PolicyChunk) Reset() { *x = PolicyChunk{} - mi := &file_openshell_proto_msgTypes[199] + mi := &file_openshell_proto_msgTypes[200] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15798,7 +15858,7 @@ func (x *PolicyChunk) String() string { func (*PolicyChunk) ProtoMessage() {} func (x *PolicyChunk) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[199] + mi := &file_openshell_proto_msgTypes[200] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15811,7 +15871,7 @@ func (x *PolicyChunk) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyChunk.ProtoReflect.Descriptor instead. func (*PolicyChunk) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{199} + return file_openshell_proto_rawDescGZIP(), []int{200} } func (x *PolicyChunk) GetId() string { @@ -15999,7 +16059,7 @@ type DraftPolicyUpdate struct { func (x *DraftPolicyUpdate) Reset() { *x = DraftPolicyUpdate{} - mi := &file_openshell_proto_msgTypes[200] + mi := &file_openshell_proto_msgTypes[201] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16011,7 +16071,7 @@ func (x *DraftPolicyUpdate) String() string { func (*DraftPolicyUpdate) ProtoMessage() {} func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[200] + mi := &file_openshell_proto_msgTypes[201] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16024,7 +16084,7 @@ func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftPolicyUpdate.ProtoReflect.Descriptor instead. func (*DraftPolicyUpdate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{200} + return file_openshell_proto_rawDescGZIP(), []int{201} } func (x *DraftPolicyUpdate) GetDraftVersion() uint64 { @@ -16083,7 +16143,7 @@ type SubmitPolicyAnalysisRequest struct { func (x *SubmitPolicyAnalysisRequest) Reset() { *x = SubmitPolicyAnalysisRequest{} - mi := &file_openshell_proto_msgTypes[201] + mi := &file_openshell_proto_msgTypes[202] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16095,7 +16155,7 @@ func (x *SubmitPolicyAnalysisRequest) String() string { func (*SubmitPolicyAnalysisRequest) ProtoMessage() {} func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[201] + mi := &file_openshell_proto_msgTypes[202] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16108,7 +16168,7 @@ func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisRequest.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{201} + return file_openshell_proto_rawDescGZIP(), []int{202} } func (x *SubmitPolicyAnalysisRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16171,7 +16231,7 @@ type SubmitPolicyAnalysisResponse struct { func (x *SubmitPolicyAnalysisResponse) Reset() { *x = SubmitPolicyAnalysisResponse{} - mi := &file_openshell_proto_msgTypes[202] + mi := &file_openshell_proto_msgTypes[203] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16183,7 +16243,7 @@ func (x *SubmitPolicyAnalysisResponse) String() string { func (*SubmitPolicyAnalysisResponse) ProtoMessage() {} func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[202] + mi := &file_openshell_proto_msgTypes[203] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16196,7 +16256,7 @@ func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisResponse.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{202} + return file_openshell_proto_rawDescGZIP(), []int{203} } func (x *SubmitPolicyAnalysisResponse) GetAcceptedChunks() uint32 { @@ -16241,7 +16301,7 @@ type GetDraftPolicyRequest struct { func (x *GetDraftPolicyRequest) Reset() { *x = GetDraftPolicyRequest{} - mi := &file_openshell_proto_msgTypes[203] + mi := &file_openshell_proto_msgTypes[204] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16253,7 +16313,7 @@ func (x *GetDraftPolicyRequest) String() string { func (*GetDraftPolicyRequest) ProtoMessage() {} func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[203] + mi := &file_openshell_proto_msgTypes[204] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16266,7 +16326,7 @@ func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyRequest.ProtoReflect.Descriptor instead. func (*GetDraftPolicyRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{203} + return file_openshell_proto_rawDescGZIP(), []int{204} } func (x *GetDraftPolicyRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16306,7 +16366,7 @@ type GetDraftPolicyResponse struct { func (x *GetDraftPolicyResponse) Reset() { *x = GetDraftPolicyResponse{} - mi := &file_openshell_proto_msgTypes[204] + mi := &file_openshell_proto_msgTypes[205] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16318,7 +16378,7 @@ func (x *GetDraftPolicyResponse) String() string { func (*GetDraftPolicyResponse) ProtoMessage() {} func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[204] + mi := &file_openshell_proto_msgTypes[205] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16331,7 +16391,7 @@ func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyResponse.ProtoReflect.Descriptor instead. func (*GetDraftPolicyResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{204} + return file_openshell_proto_rawDescGZIP(), []int{205} } func (x *GetDraftPolicyResponse) GetChunks() []*PolicyChunk { @@ -16382,7 +16442,7 @@ type ApproveDraftChunkRequest struct { func (x *ApproveDraftChunkRequest) Reset() { *x = ApproveDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[205] + mi := &file_openshell_proto_msgTypes[206] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16394,7 +16454,7 @@ func (x *ApproveDraftChunkRequest) String() string { func (*ApproveDraftChunkRequest) ProtoMessage() {} func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[205] + mi := &file_openshell_proto_msgTypes[206] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16407,7 +16467,7 @@ func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkRequest.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{205} + return file_openshell_proto_rawDescGZIP(), []int{206} } func (x *ApproveDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16457,7 +16517,7 @@ type ApproveDraftChunkResponse struct { func (x *ApproveDraftChunkResponse) Reset() { *x = ApproveDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[206] + mi := &file_openshell_proto_msgTypes[207] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16469,7 +16529,7 @@ func (x *ApproveDraftChunkResponse) String() string { func (*ApproveDraftChunkResponse) ProtoMessage() {} func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[206] + mi := &file_openshell_proto_msgTypes[207] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16482,7 +16542,7 @@ func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkResponse.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{206} + return file_openshell_proto_rawDescGZIP(), []int{207} } func (x *ApproveDraftChunkResponse) GetPolicyVersion() uint32 { @@ -16518,7 +16578,7 @@ type RejectDraftChunkRequest struct { func (x *RejectDraftChunkRequest) Reset() { *x = RejectDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[207] + mi := &file_openshell_proto_msgTypes[208] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16530,7 +16590,7 @@ func (x *RejectDraftChunkRequest) String() string { func (*RejectDraftChunkRequest) ProtoMessage() {} func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[207] + mi := &file_openshell_proto_msgTypes[208] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16543,7 +16603,7 @@ func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkRequest.ProtoReflect.Descriptor instead. func (*RejectDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{207} + return file_openshell_proto_rawDescGZIP(), []int{208} } func (x *RejectDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16589,7 +16649,7 @@ type RejectDraftChunkResponse struct { func (x *RejectDraftChunkResponse) Reset() { *x = RejectDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[208] + mi := &file_openshell_proto_msgTypes[209] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16601,7 +16661,7 @@ func (x *RejectDraftChunkResponse) String() string { func (*RejectDraftChunkResponse) ProtoMessage() {} func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[208] + mi := &file_openshell_proto_msgTypes[209] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16614,7 +16674,7 @@ func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkResponse.ProtoReflect.Descriptor instead. func (*RejectDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{208} + return file_openshell_proto_rawDescGZIP(), []int{209} } // Approve all pending chunks. @@ -16628,7 +16688,7 @@ type DraftChunkApproval struct { func (x *DraftChunkApproval) Reset() { *x = DraftChunkApproval{} - mi := &file_openshell_proto_msgTypes[209] + mi := &file_openshell_proto_msgTypes[210] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16640,7 +16700,7 @@ func (x *DraftChunkApproval) String() string { func (*DraftChunkApproval) ProtoMessage() {} func (x *DraftChunkApproval) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[209] + mi := &file_openshell_proto_msgTypes[210] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16653,7 +16713,7 @@ func (x *DraftChunkApproval) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftChunkApproval.ProtoReflect.Descriptor instead. func (*DraftChunkApproval) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{209} + return file_openshell_proto_rawDescGZIP(), []int{210} } func (x *DraftChunkApproval) GetChunkId() string { @@ -16689,7 +16749,7 @@ type ApproveAllDraftChunksRequest struct { func (x *ApproveAllDraftChunksRequest) Reset() { *x = ApproveAllDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[210] + mi := &file_openshell_proto_msgTypes[211] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16701,7 +16761,7 @@ func (x *ApproveAllDraftChunksRequest) String() string { func (*ApproveAllDraftChunksRequest) ProtoMessage() {} func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[210] + mi := &file_openshell_proto_msgTypes[211] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16714,7 +16774,7 @@ func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{210} + return file_openshell_proto_rawDescGZIP(), []int{211} } func (x *ApproveAllDraftChunksRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16769,7 +16829,7 @@ type ApproveAllDraftChunksResponse struct { func (x *ApproveAllDraftChunksResponse) Reset() { *x = ApproveAllDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[211] + mi := &file_openshell_proto_msgTypes[212] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16781,7 +16841,7 @@ func (x *ApproveAllDraftChunksResponse) String() string { func (*ApproveAllDraftChunksResponse) ProtoMessage() {} func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[211] + mi := &file_openshell_proto_msgTypes[212] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16794,7 +16854,7 @@ func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{211} + return file_openshell_proto_rawDescGZIP(), []int{212} } func (x *ApproveAllDraftChunksResponse) GetPolicyVersion() uint32 { @@ -16844,7 +16904,7 @@ type EditDraftChunkRequest struct { func (x *EditDraftChunkRequest) Reset() { *x = EditDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[212] + mi := &file_openshell_proto_msgTypes[213] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16856,7 +16916,7 @@ func (x *EditDraftChunkRequest) String() string { func (*EditDraftChunkRequest) ProtoMessage() {} func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[212] + mi := &file_openshell_proto_msgTypes[213] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16869,7 +16929,7 @@ func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkRequest.ProtoReflect.Descriptor instead. func (*EditDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{212} + return file_openshell_proto_rawDescGZIP(), []int{213} } func (x *EditDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16915,7 +16975,7 @@ type EditDraftChunkResponse struct { func (x *EditDraftChunkResponse) Reset() { *x = EditDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[213] + mi := &file_openshell_proto_msgTypes[214] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16927,7 +16987,7 @@ func (x *EditDraftChunkResponse) String() string { func (*EditDraftChunkResponse) ProtoMessage() {} func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[213] + mi := &file_openshell_proto_msgTypes[214] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16940,7 +17000,7 @@ func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkResponse.ProtoReflect.Descriptor instead. func (*EditDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{213} + return file_openshell_proto_rawDescGZIP(), []int{214} } // Reverse an approval (remove merged rule from active policy). @@ -16960,7 +17020,7 @@ type UndoDraftChunkRequest struct { func (x *UndoDraftChunkRequest) Reset() { *x = UndoDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[214] + mi := &file_openshell_proto_msgTypes[215] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16972,7 +17032,7 @@ func (x *UndoDraftChunkRequest) String() string { func (*UndoDraftChunkRequest) ProtoMessage() {} func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[214] + mi := &file_openshell_proto_msgTypes[215] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16985,7 +17045,7 @@ func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkRequest.ProtoReflect.Descriptor instead. func (*UndoDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{214} + return file_openshell_proto_rawDescGZIP(), []int{215} } func (x *UndoDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -17028,7 +17088,7 @@ type UndoDraftChunkResponse struct { func (x *UndoDraftChunkResponse) Reset() { *x = UndoDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[215] + mi := &file_openshell_proto_msgTypes[216] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17040,7 +17100,7 @@ func (x *UndoDraftChunkResponse) String() string { func (*UndoDraftChunkResponse) ProtoMessage() {} func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[215] + mi := &file_openshell_proto_msgTypes[216] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17053,7 +17113,7 @@ func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkResponse.ProtoReflect.Descriptor instead. func (*UndoDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{215} + return file_openshell_proto_rawDescGZIP(), []int{216} } func (x *UndoDraftChunkResponse) GetPolicyVersion() uint32 { @@ -17085,7 +17145,7 @@ type ClearDraftChunksRequest struct { func (x *ClearDraftChunksRequest) Reset() { *x = ClearDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[216] + mi := &file_openshell_proto_msgTypes[217] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17097,7 +17157,7 @@ func (x *ClearDraftChunksRequest) String() string { func (*ClearDraftChunksRequest) ProtoMessage() {} func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[216] + mi := &file_openshell_proto_msgTypes[217] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17110,7 +17170,7 @@ func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ClearDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{216} + return file_openshell_proto_rawDescGZIP(), []int{217} } func (x *ClearDraftChunksRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -17144,7 +17204,7 @@ type ClearDraftChunksResponse struct { func (x *ClearDraftChunksResponse) Reset() { *x = ClearDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[217] + mi := &file_openshell_proto_msgTypes[218] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17156,7 +17216,7 @@ func (x *ClearDraftChunksResponse) String() string { func (*ClearDraftChunksResponse) ProtoMessage() {} func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[217] + mi := &file_openshell_proto_msgTypes[218] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17169,7 +17229,7 @@ func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ClearDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{217} + return file_openshell_proto_rawDescGZIP(), []int{218} } func (x *ClearDraftChunksResponse) GetChunksCleared() uint32 { @@ -17191,7 +17251,7 @@ type GetDraftHistoryRequest struct { func (x *GetDraftHistoryRequest) Reset() { *x = GetDraftHistoryRequest{} - mi := &file_openshell_proto_msgTypes[218] + mi := &file_openshell_proto_msgTypes[219] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17203,7 +17263,7 @@ func (x *GetDraftHistoryRequest) String() string { func (*GetDraftHistoryRequest) ProtoMessage() {} func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[218] + mi := &file_openshell_proto_msgTypes[219] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17216,7 +17276,7 @@ func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryRequest.ProtoReflect.Descriptor instead. func (*GetDraftHistoryRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{218} + return file_openshell_proto_rawDescGZIP(), []int{219} } func (x *GetDraftHistoryRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -17250,7 +17310,7 @@ type DraftHistoryEntry struct { func (x *DraftHistoryEntry) Reset() { *x = DraftHistoryEntry{} - mi := &file_openshell_proto_msgTypes[219] + mi := &file_openshell_proto_msgTypes[220] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17262,7 +17322,7 @@ func (x *DraftHistoryEntry) String() string { func (*DraftHistoryEntry) ProtoMessage() {} func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[219] + mi := &file_openshell_proto_msgTypes[220] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17275,7 +17335,7 @@ func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftHistoryEntry.ProtoReflect.Descriptor instead. func (*DraftHistoryEntry) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{219} + return file_openshell_proto_rawDescGZIP(), []int{220} } func (x *DraftHistoryEntry) GetEventTime() *timestamppb.Timestamp { @@ -17316,7 +17376,7 @@ type GetDraftHistoryResponse struct { func (x *GetDraftHistoryResponse) Reset() { *x = GetDraftHistoryResponse{} - mi := &file_openshell_proto_msgTypes[220] + mi := &file_openshell_proto_msgTypes[221] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17328,7 +17388,7 @@ func (x *GetDraftHistoryResponse) String() string { func (*GetDraftHistoryResponse) ProtoMessage() {} func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[220] + mi := &file_openshell_proto_msgTypes[221] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17341,7 +17401,7 @@ func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryResponse.ProtoReflect.Descriptor instead. func (*GetDraftHistoryResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{220} + return file_openshell_proto_rawDescGZIP(), []int{221} } func (x *GetDraftHistoryResponse) GetEntries() []*DraftHistoryEntry { @@ -17366,7 +17426,7 @@ type CreateWorkspaceRequest struct { func (x *CreateWorkspaceRequest) Reset() { *x = CreateWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[221] + mi := &file_openshell_proto_msgTypes[222] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17378,7 +17438,7 @@ func (x *CreateWorkspaceRequest) String() string { func (*CreateWorkspaceRequest) ProtoMessage() {} func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[221] + mi := &file_openshell_proto_msgTypes[222] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17391,7 +17451,7 @@ func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceRequest.ProtoReflect.Descriptor instead. func (*CreateWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{221} + return file_openshell_proto_rawDescGZIP(), []int{222} } func (x *CreateWorkspaceRequest) GetName() string { @@ -17425,7 +17485,7 @@ type CreateWorkspaceResponse struct { func (x *CreateWorkspaceResponse) Reset() { *x = CreateWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[222] + mi := &file_openshell_proto_msgTypes[223] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17437,7 +17497,7 @@ func (x *CreateWorkspaceResponse) String() string { func (*CreateWorkspaceResponse) ProtoMessage() {} func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[222] + mi := &file_openshell_proto_msgTypes[223] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17450,7 +17510,7 @@ func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceResponse.ProtoReflect.Descriptor instead. func (*CreateWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{222} + return file_openshell_proto_rawDescGZIP(), []int{223} } func (x *CreateWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -17471,7 +17531,7 @@ type GetWorkspaceRequest struct { func (x *GetWorkspaceRequest) Reset() { *x = GetWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[223] + mi := &file_openshell_proto_msgTypes[224] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17483,7 +17543,7 @@ func (x *GetWorkspaceRequest) String() string { func (*GetWorkspaceRequest) ProtoMessage() {} func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[223] + mi := &file_openshell_proto_msgTypes[224] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17496,7 +17556,7 @@ func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceRequest.ProtoReflect.Descriptor instead. func (*GetWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{223} + return file_openshell_proto_rawDescGZIP(), []int{224} } func (x *GetWorkspaceRequest) GetName() string { @@ -17516,7 +17576,7 @@ type GetWorkspaceResponse struct { func (x *GetWorkspaceResponse) Reset() { *x = GetWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[224] + mi := &file_openshell_proto_msgTypes[225] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17528,7 +17588,7 @@ func (x *GetWorkspaceResponse) String() string { func (*GetWorkspaceResponse) ProtoMessage() {} func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[224] + mi := &file_openshell_proto_msgTypes[225] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17541,7 +17601,7 @@ func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceResponse.ProtoReflect.Descriptor instead. func (*GetWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{224} + return file_openshell_proto_rawDescGZIP(), []int{225} } func (x *GetWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -17568,7 +17628,7 @@ type ListWorkspacesRequest struct { func (x *ListWorkspacesRequest) Reset() { *x = ListWorkspacesRequest{} - mi := &file_openshell_proto_msgTypes[225] + mi := &file_openshell_proto_msgTypes[226] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17580,7 +17640,7 @@ func (x *ListWorkspacesRequest) String() string { func (*ListWorkspacesRequest) ProtoMessage() {} func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[225] + mi := &file_openshell_proto_msgTypes[226] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17593,7 +17653,7 @@ func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesRequest.ProtoReflect.Descriptor instead. func (*ListWorkspacesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{225} + return file_openshell_proto_rawDescGZIP(), []int{226} } func (x *ListWorkspacesRequest) GetPageSize() int32 { @@ -17629,7 +17689,7 @@ type ListWorkspacesResponse struct { func (x *ListWorkspacesResponse) Reset() { *x = ListWorkspacesResponse{} - mi := &file_openshell_proto_msgTypes[226] + mi := &file_openshell_proto_msgTypes[227] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17641,7 +17701,7 @@ func (x *ListWorkspacesResponse) String() string { func (*ListWorkspacesResponse) ProtoMessage() {} func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[226] + mi := &file_openshell_proto_msgTypes[227] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17654,7 +17714,7 @@ func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesResponse.ProtoReflect.Descriptor instead. func (*ListWorkspacesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{226} + return file_openshell_proto_rawDescGZIP(), []int{227} } func (x *ListWorkspacesResponse) GetWorkspaces() []*datamodelv1.Workspace { @@ -17685,7 +17745,7 @@ type DeleteWorkspaceRequest struct { func (x *DeleteWorkspaceRequest) Reset() { *x = DeleteWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[227] + mi := &file_openshell_proto_msgTypes[228] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17697,7 +17757,7 @@ func (x *DeleteWorkspaceRequest) String() string { func (*DeleteWorkspaceRequest) ProtoMessage() {} func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[227] + mi := &file_openshell_proto_msgTypes[228] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17710,7 +17770,7 @@ func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceRequest.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{227} + return file_openshell_proto_rawDescGZIP(), []int{228} } func (x *DeleteWorkspaceRequest) GetName() string { @@ -17744,7 +17804,7 @@ type DeleteWorkspaceResponse struct { func (x *DeleteWorkspaceResponse) Reset() { *x = DeleteWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[228] + mi := &file_openshell_proto_msgTypes[229] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17756,7 +17816,7 @@ func (x *DeleteWorkspaceResponse) String() string { func (*DeleteWorkspaceResponse) ProtoMessage() {} func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[228] + mi := &file_openshell_proto_msgTypes[229] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17769,7 +17829,7 @@ func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceResponse.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{228} + return file_openshell_proto_rawDescGZIP(), []int{229} } func (x *DeleteWorkspaceResponse) GetOutcome() DeletionOutcome { @@ -17793,7 +17853,7 @@ type WorkspaceMember struct { func (x *WorkspaceMember) Reset() { *x = WorkspaceMember{} - mi := &file_openshell_proto_msgTypes[229] + mi := &file_openshell_proto_msgTypes[230] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17805,7 +17865,7 @@ func (x *WorkspaceMember) String() string { func (*WorkspaceMember) ProtoMessage() {} func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[229] + mi := &file_openshell_proto_msgTypes[230] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17818,7 +17878,7 @@ func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspaceMember.ProtoReflect.Descriptor instead. func (*WorkspaceMember) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{229} + return file_openshell_proto_rawDescGZIP(), []int{230} } func (x *WorkspaceMember) GetMetadata() *datamodelv1.ObjectMeta { @@ -17859,7 +17919,7 @@ type AddWorkspaceMemberRequest struct { func (x *AddWorkspaceMemberRequest) Reset() { *x = AddWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[230] + mi := &file_openshell_proto_msgTypes[231] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17871,7 +17931,7 @@ func (x *AddWorkspaceMemberRequest) String() string { func (*AddWorkspaceMemberRequest) ProtoMessage() {} func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[230] + mi := &file_openshell_proto_msgTypes[231] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17884,7 +17944,7 @@ func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{230} + return file_openshell_proto_rawDescGZIP(), []int{231} } func (x *AddWorkspaceMemberRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -17925,7 +17985,7 @@ type AddWorkspaceMemberResponse struct { func (x *AddWorkspaceMemberResponse) Reset() { *x = AddWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[231] + mi := &file_openshell_proto_msgTypes[232] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17937,7 +17997,7 @@ func (x *AddWorkspaceMemberResponse) String() string { func (*AddWorkspaceMemberResponse) ProtoMessage() {} func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[231] + mi := &file_openshell_proto_msgTypes[232] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17950,7 +18010,7 @@ func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{231} + return file_openshell_proto_rawDescGZIP(), []int{232} } func (x *AddWorkspaceMemberResponse) GetMember() *WorkspaceMember { @@ -17976,7 +18036,7 @@ type RemoveWorkspaceMemberRequest struct { func (x *RemoveWorkspaceMemberRequest) Reset() { *x = RemoveWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[232] + mi := &file_openshell_proto_msgTypes[233] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17988,7 +18048,7 @@ func (x *RemoveWorkspaceMemberRequest) String() string { func (*RemoveWorkspaceMemberRequest) ProtoMessage() {} func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[232] + mi := &file_openshell_proto_msgTypes[233] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18001,7 +18061,7 @@ func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{232} + return file_openshell_proto_rawDescGZIP(), []int{233} } func (x *RemoveWorkspaceMemberRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -18042,7 +18102,7 @@ type RemoveWorkspaceMemberResponse struct { func (x *RemoveWorkspaceMemberResponse) Reset() { *x = RemoveWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[233] + mi := &file_openshell_proto_msgTypes[234] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18054,7 +18114,7 @@ func (x *RemoveWorkspaceMemberResponse) String() string { func (*RemoveWorkspaceMemberResponse) ProtoMessage() {} func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[233] + mi := &file_openshell_proto_msgTypes[234] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18067,7 +18127,7 @@ func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{233} + return file_openshell_proto_rawDescGZIP(), []int{234} } func (x *RemoveWorkspaceMemberResponse) GetOutcome() DeletionOutcome { @@ -18094,7 +18154,7 @@ type ListWorkspaceMembersRequest struct { func (x *ListWorkspaceMembersRequest) Reset() { *x = ListWorkspaceMembersRequest{} - mi := &file_openshell_proto_msgTypes[234] + mi := &file_openshell_proto_msgTypes[235] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18106,7 +18166,7 @@ func (x *ListWorkspaceMembersRequest) String() string { func (*ListWorkspaceMembersRequest) ProtoMessage() {} func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[234] + mi := &file_openshell_proto_msgTypes[235] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18119,7 +18179,7 @@ func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersRequest.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{234} + return file_openshell_proto_rawDescGZIP(), []int{235} } func (x *ListWorkspaceMembersRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -18155,7 +18215,7 @@ type ListWorkspaceMembersResponse struct { func (x *ListWorkspaceMembersResponse) Reset() { *x = ListWorkspaceMembersResponse{} - mi := &file_openshell_proto_msgTypes[235] + mi := &file_openshell_proto_msgTypes[236] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18167,7 +18227,7 @@ func (x *ListWorkspaceMembersResponse) String() string { func (*ListWorkspaceMembersResponse) ProtoMessage() {} func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[235] + mi := &file_openshell_proto_msgTypes[236] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18180,7 +18240,7 @@ func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersResponse.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{235} + return file_openshell_proto_rawDescGZIP(), []int{236} } func (x *ListWorkspaceMembersResponse) GetMembers() []*WorkspaceMember { @@ -18215,7 +18275,7 @@ type ExtensionServiceCredential struct { func (x *ExtensionServiceCredential) Reset() { *x = ExtensionServiceCredential{} - mi := &file_openshell_proto_msgTypes[236] + mi := &file_openshell_proto_msgTypes[237] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18227,7 +18287,7 @@ func (x *ExtensionServiceCredential) String() string { func (*ExtensionServiceCredential) ProtoMessage() {} func (x *ExtensionServiceCredential) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[236] + mi := &file_openshell_proto_msgTypes[237] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18240,7 +18300,7 @@ func (x *ExtensionServiceCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use ExtensionServiceCredential.ProtoReflect.Descriptor instead. func (*ExtensionServiceCredential) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{236} + return file_openshell_proto_rawDescGZIP(), []int{237} } func (x *ExtensionServiceCredential) GetServiceName() string { @@ -18277,7 +18337,7 @@ type EndpointObservation struct { func (x *EndpointObservation) Reset() { *x = EndpointObservation{} - mi := &file_openshell_proto_msgTypes[237] + mi := &file_openshell_proto_msgTypes[238] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18289,7 +18349,7 @@ func (x *EndpointObservation) String() string { func (*EndpointObservation) ProtoMessage() {} func (x *EndpointObservation) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[237] + mi := &file_openshell_proto_msgTypes[238] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18302,7 +18362,7 @@ func (x *EndpointObservation) ProtoReflect() protoreflect.Message { // Deprecated: Use EndpointObservation.ProtoReflect.Descriptor instead. func (*EndpointObservation) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{237} + return file_openshell_proto_rawDescGZIP(), []int{238} } func (x *EndpointObservation) GetEndpointId() string { @@ -18345,7 +18405,7 @@ type ReportEndpointStatusRequest struct { func (x *ReportEndpointStatusRequest) Reset() { *x = ReportEndpointStatusRequest{} - mi := &file_openshell_proto_msgTypes[238] + mi := &file_openshell_proto_msgTypes[239] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18357,7 +18417,7 @@ func (x *ReportEndpointStatusRequest) String() string { func (*ReportEndpointStatusRequest) ProtoMessage() {} func (x *ReportEndpointStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[238] + mi := &file_openshell_proto_msgTypes[239] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18370,7 +18430,7 @@ func (x *ReportEndpointStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportEndpointStatusRequest.ProtoReflect.Descriptor instead. func (*ReportEndpointStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{238} + return file_openshell_proto_rawDescGZIP(), []int{239} } func (x *ReportEndpointStatusRequest) GetSandboxId() string { @@ -18431,7 +18491,7 @@ type ReportEndpointStatusResponse struct { func (x *ReportEndpointStatusResponse) Reset() { *x = ReportEndpointStatusResponse{} - mi := &file_openshell_proto_msgTypes[239] + mi := &file_openshell_proto_msgTypes[240] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18443,7 +18503,7 @@ func (x *ReportEndpointStatusResponse) String() string { func (*ReportEndpointStatusResponse) ProtoMessage() {} func (x *ReportEndpointStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[239] + mi := &file_openshell_proto_msgTypes[240] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18456,7 +18516,7 @@ func (x *ReportEndpointStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportEndpointStatusResponse.ProtoReflect.Descriptor instead. func (*ReportEndpointStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{239} + return file_openshell_proto_rawDescGZIP(), []int{240} } // A configured endpoint and its last accepted network result in one record. @@ -18485,7 +18545,7 @@ type EndpointStatus struct { func (x *EndpointStatus) Reset() { *x = EndpointStatus{} - mi := &file_openshell_proto_msgTypes[240] + mi := &file_openshell_proto_msgTypes[241] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18497,7 +18557,7 @@ func (x *EndpointStatus) String() string { func (*EndpointStatus) ProtoMessage() {} func (x *EndpointStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[240] + mi := &file_openshell_proto_msgTypes[241] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18510,7 +18570,7 @@ func (x *EndpointStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use EndpointStatus.ProtoReflect.Descriptor instead. func (*EndpointStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{240} + return file_openshell_proto_rawDescGZIP(), []int{241} } func (x *EndpointStatus) GetEndpointId() string { @@ -18580,7 +18640,7 @@ type SandboxProvisioning struct { func (x *SandboxProvisioning) Reset() { *x = SandboxProvisioning{} - mi := &file_openshell_proto_msgTypes[241] + mi := &file_openshell_proto_msgTypes[242] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18592,7 +18652,7 @@ func (x *SandboxProvisioning) String() string { func (*SandboxProvisioning) ProtoMessage() {} func (x *SandboxProvisioning) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[241] + mi := &file_openshell_proto_msgTypes[242] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18605,7 +18665,7 @@ func (x *SandboxProvisioning) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxProvisioning.ProtoReflect.Descriptor instead. func (*SandboxProvisioning) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{241} + return file_openshell_proto_rawDescGZIP(), []int{242} } func (x *SandboxProvisioning) GetAttemptId() string { @@ -18700,7 +18760,7 @@ type SandboxServiceExposure struct { func (x *SandboxServiceExposure) Reset() { *x = SandboxServiceExposure{} - mi := &file_openshell_proto_msgTypes[242] + mi := &file_openshell_proto_msgTypes[243] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18712,7 +18772,7 @@ func (x *SandboxServiceExposure) String() string { func (*SandboxServiceExposure) ProtoMessage() {} func (x *SandboxServiceExposure) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[242] + mi := &file_openshell_proto_msgTypes[243] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18725,7 +18785,7 @@ func (x *SandboxServiceExposure) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxServiceExposure.ProtoReflect.Descriptor instead. func (*SandboxServiceExposure) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{242} + return file_openshell_proto_rawDescGZIP(), []int{243} } func (x *SandboxServiceExposure) GetService() string { @@ -19862,19 +19922,21 @@ const file_openshell_proto_rawDesc = "" + "\n" + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x1d\n" + "\n" + - "session_id\x18\x02 \x01(\tR\tsessionId\"\x8a\x02\n" + + "session_id\x18\x02 \x01(\tR\tsessionId\"L\n" + + "\x18PeerConfigProviderTarget\x12\x1c\n" + + "\tworkspace\x18\x01 \x01(\tR\tworkspace\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\"\xd0\x02\n" + "\x1bPeerConfigUpdateHintRequest\x12A\n" + "\asandbox\x18\x01 \x01(\v2%.openshell.v1.PeerConfigSandboxTargetH\x00R\asandbox\x12\x1e\n" + "\tworkspace\x18\x02 \x01(\tH\x00R\tworkspace\x12%\n" + - "\rall_connected\x18\x03 \x01(\bH\x00R\fallConnected\x12%\n" + + "\rall_connected\x18\x03 \x01(\bH\x00R\fallConnected\x12D\n" + + "\bprovider\x18\x06 \x01(\v2&.openshell.v1.PeerConfigProviderTargetH\x00R\bprovider\x12%\n" + "\x0esandbox_config\x18\x04 \x01(\bR\rsandboxConfig\x121\n" + "\x14provider_environment\x18\x05 \x01(\bR\x13providerEnvironmentB\a\n" + - "\x05scope\"^\n" + + "\x05scope\"?\n" + "\x1cPeerConfigUpdateHintResponse\x12\x1f\n" + "\vstale_owner\x18\x01 \x01(\bR\n" + - "staleOwner\x12\x1d\n" + - "\n" + - "queue_full\x18\x02 \x01(\bR\tqueueFull\"`\n" + + "staleOwner\"`\n" + "\x0fRelayOpenResult\x12\x1d\n" + "\n" + "channel_id\x18\x01 \x01(\tR\tchannelId\x12\x18\n" + @@ -20493,7 +20555,7 @@ func file_openshell_proto_rawDescGZIP() []byte { } var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 21) -var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 268) +var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 269) var file_openshell_proto_goTypes = []any{ (ExtensionKind)(0), // 0: openshell.v1.ExtensionKind (SandboxPhase)(0), // 1: openshell.v1.SandboxPhase @@ -20707,109 +20769,110 @@ var file_openshell_proto_goTypes = []any{ (*PeerRelayInit)(nil), // 209: openshell.v1.PeerRelayInit (*PeerRelayFrame)(nil), // 210: openshell.v1.PeerRelayFrame (*PeerConfigSandboxTarget)(nil), // 211: openshell.v1.PeerConfigSandboxTarget - (*PeerConfigUpdateHintRequest)(nil), // 212: openshell.v1.PeerConfigUpdateHintRequest - (*PeerConfigUpdateHintResponse)(nil), // 213: openshell.v1.PeerConfigUpdateHintResponse - (*RelayOpenResult)(nil), // 214: openshell.v1.RelayOpenResult - (*RelayClose)(nil), // 215: openshell.v1.RelayClose - (*L7RequestSample)(nil), // 216: openshell.v1.L7RequestSample - (*DenialSummary)(nil), // 217: openshell.v1.DenialSummary - (*DenialGroupCount)(nil), // 218: openshell.v1.DenialGroupCount - (*NetworkActivitySummary)(nil), // 219: openshell.v1.NetworkActivitySummary - (*PolicyChunk)(nil), // 220: openshell.v1.PolicyChunk - (*DraftPolicyUpdate)(nil), // 221: openshell.v1.DraftPolicyUpdate - (*SubmitPolicyAnalysisRequest)(nil), // 222: openshell.v1.SubmitPolicyAnalysisRequest - (*SubmitPolicyAnalysisResponse)(nil), // 223: openshell.v1.SubmitPolicyAnalysisResponse - (*GetDraftPolicyRequest)(nil), // 224: openshell.v1.GetDraftPolicyRequest - (*GetDraftPolicyResponse)(nil), // 225: openshell.v1.GetDraftPolicyResponse - (*ApproveDraftChunkRequest)(nil), // 226: openshell.v1.ApproveDraftChunkRequest - (*ApproveDraftChunkResponse)(nil), // 227: openshell.v1.ApproveDraftChunkResponse - (*RejectDraftChunkRequest)(nil), // 228: openshell.v1.RejectDraftChunkRequest - (*RejectDraftChunkResponse)(nil), // 229: openshell.v1.RejectDraftChunkResponse - (*DraftChunkApproval)(nil), // 230: openshell.v1.DraftChunkApproval - (*ApproveAllDraftChunksRequest)(nil), // 231: openshell.v1.ApproveAllDraftChunksRequest - (*ApproveAllDraftChunksResponse)(nil), // 232: openshell.v1.ApproveAllDraftChunksResponse - (*EditDraftChunkRequest)(nil), // 233: openshell.v1.EditDraftChunkRequest - (*EditDraftChunkResponse)(nil), // 234: openshell.v1.EditDraftChunkResponse - (*UndoDraftChunkRequest)(nil), // 235: openshell.v1.UndoDraftChunkRequest - (*UndoDraftChunkResponse)(nil), // 236: openshell.v1.UndoDraftChunkResponse - (*ClearDraftChunksRequest)(nil), // 237: openshell.v1.ClearDraftChunksRequest - (*ClearDraftChunksResponse)(nil), // 238: openshell.v1.ClearDraftChunksResponse - (*GetDraftHistoryRequest)(nil), // 239: openshell.v1.GetDraftHistoryRequest - (*DraftHistoryEntry)(nil), // 240: openshell.v1.DraftHistoryEntry - (*GetDraftHistoryResponse)(nil), // 241: openshell.v1.GetDraftHistoryResponse - (*CreateWorkspaceRequest)(nil), // 242: openshell.v1.CreateWorkspaceRequest - (*CreateWorkspaceResponse)(nil), // 243: openshell.v1.CreateWorkspaceResponse - (*GetWorkspaceRequest)(nil), // 244: openshell.v1.GetWorkspaceRequest - (*GetWorkspaceResponse)(nil), // 245: openshell.v1.GetWorkspaceResponse - (*ListWorkspacesRequest)(nil), // 246: openshell.v1.ListWorkspacesRequest - (*ListWorkspacesResponse)(nil), // 247: openshell.v1.ListWorkspacesResponse - (*DeleteWorkspaceRequest)(nil), // 248: openshell.v1.DeleteWorkspaceRequest - (*DeleteWorkspaceResponse)(nil), // 249: openshell.v1.DeleteWorkspaceResponse - (*WorkspaceMember)(nil), // 250: openshell.v1.WorkspaceMember - (*AddWorkspaceMemberRequest)(nil), // 251: openshell.v1.AddWorkspaceMemberRequest - (*AddWorkspaceMemberResponse)(nil), // 252: openshell.v1.AddWorkspaceMemberResponse - (*RemoveWorkspaceMemberRequest)(nil), // 253: openshell.v1.RemoveWorkspaceMemberRequest - (*RemoveWorkspaceMemberResponse)(nil), // 254: openshell.v1.RemoveWorkspaceMemberResponse - (*ListWorkspaceMembersRequest)(nil), // 255: openshell.v1.ListWorkspaceMembersRequest - (*ListWorkspaceMembersResponse)(nil), // 256: openshell.v1.ListWorkspaceMembersResponse - (*ExtensionServiceCredential)(nil), // 257: openshell.v1.ExtensionServiceCredential - (*EndpointObservation)(nil), // 258: openshell.v1.EndpointObservation - (*ReportEndpointStatusRequest)(nil), // 259: openshell.v1.ReportEndpointStatusRequest - (*ReportEndpointStatusResponse)(nil), // 260: openshell.v1.ReportEndpointStatusResponse - (*EndpointStatus)(nil), // 261: openshell.v1.EndpointStatus - (*SandboxProvisioning)(nil), // 262: openshell.v1.SandboxProvisioning - (*SandboxServiceExposure)(nil), // 263: openshell.v1.SandboxServiceExposure - nil, // 264: openshell.v1.SandboxSpec.EnvironmentEntry - nil, // 265: openshell.v1.SandboxTemplate.LabelsEntry - nil, // 266: openshell.v1.SandboxTemplate.AnnotationsEntry - nil, // 267: openshell.v1.SandboxTemplate.EnvironmentEntry - nil, // 268: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry - nil, // 269: openshell.v1.PlatformEvent.MetadataEntry - nil, // 270: openshell.v1.CreateSandboxRequest.LabelsEntry - nil, // 271: openshell.v1.CreateSandboxRequest.AnnotationsEntry - nil, // 272: openshell.v1.SandboxResponse.ServiceUrlsEntry - nil, // 273: openshell.v1.ExecSandboxRequest.EnvironmentEntry - nil, // 274: openshell.v1.SandboxLogLine.FieldsEntry - nil, // 275: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - nil, // 276: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - nil, // 277: openshell.v1.ProviderProfile.AnnotationsEntry - nil, // 278: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - nil, // 279: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - nil, // 280: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - nil, // 281: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - nil, // 282: openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry - nil, // 283: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry - nil, // 284: openshell.v1.ProviderEnvironmentSnapshot.FilesEntry - nil, // 285: openshell.v1.UpdateConfigRequest.AnnotationsEntry - nil, // 286: openshell.v1.UpdateConfigResponse.AnnotationsEntry - nil, // 287: openshell.v1.SandboxPolicyRevision.ProvenanceEntry - nil, // 288: openshell.v1.CreateWorkspaceRequest.LabelsEntry - (*timestamppb.Timestamp)(nil), // 289: google.protobuf.Timestamp - (*datamodelv1.ObjectMeta)(nil), // 290: openshell.datamodel.v1.ObjectMeta - (*sandboxv1.SandboxPolicy)(nil), // 291: openshell.sandbox.v1.SandboxPolicy - (*structpb.Struct)(nil), // 292: google.protobuf.Struct - (*durationpb.Duration)(nil), // 293: google.protobuf.Duration - (*datamodelv1.WorkspaceSelector)(nil), // 294: openshell.datamodel.v1.WorkspaceSelector - (*datamodelv1.Provider)(nil), // 295: openshell.datamodel.v1.Provider - (sandboxv1.PolicySource)(0), // 296: openshell.sandbox.v1.PolicySource - (*sandboxv1.NetworkEndpoint)(nil), // 297: openshell.sandbox.v1.NetworkEndpoint - (*sandboxv1.NetworkBinary)(nil), // 298: openshell.sandbox.v1.NetworkBinary - (*sandboxv1.SettingValue)(nil), // 299: openshell.sandbox.v1.SettingValue - (*sandboxv1.NetworkPolicyRule)(nil), // 300: openshell.sandbox.v1.NetworkPolicyRule - (*sandboxv1.L7DenyRule)(nil), // 301: openshell.sandbox.v1.L7DenyRule - (*sandboxv1.L7Rule)(nil), // 302: openshell.sandbox.v1.L7Rule - (*sandboxv1.SandboxConfigSnapshot)(nil), // 303: openshell.sandbox.v1.SandboxConfigSnapshot - (*datamodelv1.Workspace)(nil), // 304: openshell.datamodel.v1.Workspace - (*sandboxv1.GetSandboxConfigRequest)(nil), // 305: openshell.sandbox.v1.GetSandboxConfigRequest - (*sandboxv1.GetGatewayConfigRequest)(nil), // 306: openshell.sandbox.v1.GetGatewayConfigRequest - (*sandboxv1.GetSandboxConfigResponse)(nil), // 307: openshell.sandbox.v1.GetSandboxConfigResponse - (*sandboxv1.GetGatewayConfigResponse)(nil), // 308: openshell.sandbox.v1.GetGatewayConfigResponse + (*PeerConfigProviderTarget)(nil), // 212: openshell.v1.PeerConfigProviderTarget + (*PeerConfigUpdateHintRequest)(nil), // 213: openshell.v1.PeerConfigUpdateHintRequest + (*PeerConfigUpdateHintResponse)(nil), // 214: openshell.v1.PeerConfigUpdateHintResponse + (*RelayOpenResult)(nil), // 215: openshell.v1.RelayOpenResult + (*RelayClose)(nil), // 216: openshell.v1.RelayClose + (*L7RequestSample)(nil), // 217: openshell.v1.L7RequestSample + (*DenialSummary)(nil), // 218: openshell.v1.DenialSummary + (*DenialGroupCount)(nil), // 219: openshell.v1.DenialGroupCount + (*NetworkActivitySummary)(nil), // 220: openshell.v1.NetworkActivitySummary + (*PolicyChunk)(nil), // 221: openshell.v1.PolicyChunk + (*DraftPolicyUpdate)(nil), // 222: openshell.v1.DraftPolicyUpdate + (*SubmitPolicyAnalysisRequest)(nil), // 223: openshell.v1.SubmitPolicyAnalysisRequest + (*SubmitPolicyAnalysisResponse)(nil), // 224: openshell.v1.SubmitPolicyAnalysisResponse + (*GetDraftPolicyRequest)(nil), // 225: openshell.v1.GetDraftPolicyRequest + (*GetDraftPolicyResponse)(nil), // 226: openshell.v1.GetDraftPolicyResponse + (*ApproveDraftChunkRequest)(nil), // 227: openshell.v1.ApproveDraftChunkRequest + (*ApproveDraftChunkResponse)(nil), // 228: openshell.v1.ApproveDraftChunkResponse + (*RejectDraftChunkRequest)(nil), // 229: openshell.v1.RejectDraftChunkRequest + (*RejectDraftChunkResponse)(nil), // 230: openshell.v1.RejectDraftChunkResponse + (*DraftChunkApproval)(nil), // 231: openshell.v1.DraftChunkApproval + (*ApproveAllDraftChunksRequest)(nil), // 232: openshell.v1.ApproveAllDraftChunksRequest + (*ApproveAllDraftChunksResponse)(nil), // 233: openshell.v1.ApproveAllDraftChunksResponse + (*EditDraftChunkRequest)(nil), // 234: openshell.v1.EditDraftChunkRequest + (*EditDraftChunkResponse)(nil), // 235: openshell.v1.EditDraftChunkResponse + (*UndoDraftChunkRequest)(nil), // 236: openshell.v1.UndoDraftChunkRequest + (*UndoDraftChunkResponse)(nil), // 237: openshell.v1.UndoDraftChunkResponse + (*ClearDraftChunksRequest)(nil), // 238: openshell.v1.ClearDraftChunksRequest + (*ClearDraftChunksResponse)(nil), // 239: openshell.v1.ClearDraftChunksResponse + (*GetDraftHistoryRequest)(nil), // 240: openshell.v1.GetDraftHistoryRequest + (*DraftHistoryEntry)(nil), // 241: openshell.v1.DraftHistoryEntry + (*GetDraftHistoryResponse)(nil), // 242: openshell.v1.GetDraftHistoryResponse + (*CreateWorkspaceRequest)(nil), // 243: openshell.v1.CreateWorkspaceRequest + (*CreateWorkspaceResponse)(nil), // 244: openshell.v1.CreateWorkspaceResponse + (*GetWorkspaceRequest)(nil), // 245: openshell.v1.GetWorkspaceRequest + (*GetWorkspaceResponse)(nil), // 246: openshell.v1.GetWorkspaceResponse + (*ListWorkspacesRequest)(nil), // 247: openshell.v1.ListWorkspacesRequest + (*ListWorkspacesResponse)(nil), // 248: openshell.v1.ListWorkspacesResponse + (*DeleteWorkspaceRequest)(nil), // 249: openshell.v1.DeleteWorkspaceRequest + (*DeleteWorkspaceResponse)(nil), // 250: openshell.v1.DeleteWorkspaceResponse + (*WorkspaceMember)(nil), // 251: openshell.v1.WorkspaceMember + (*AddWorkspaceMemberRequest)(nil), // 252: openshell.v1.AddWorkspaceMemberRequest + (*AddWorkspaceMemberResponse)(nil), // 253: openshell.v1.AddWorkspaceMemberResponse + (*RemoveWorkspaceMemberRequest)(nil), // 254: openshell.v1.RemoveWorkspaceMemberRequest + (*RemoveWorkspaceMemberResponse)(nil), // 255: openshell.v1.RemoveWorkspaceMemberResponse + (*ListWorkspaceMembersRequest)(nil), // 256: openshell.v1.ListWorkspaceMembersRequest + (*ListWorkspaceMembersResponse)(nil), // 257: openshell.v1.ListWorkspaceMembersResponse + (*ExtensionServiceCredential)(nil), // 258: openshell.v1.ExtensionServiceCredential + (*EndpointObservation)(nil), // 259: openshell.v1.EndpointObservation + (*ReportEndpointStatusRequest)(nil), // 260: openshell.v1.ReportEndpointStatusRequest + (*ReportEndpointStatusResponse)(nil), // 261: openshell.v1.ReportEndpointStatusResponse + (*EndpointStatus)(nil), // 262: openshell.v1.EndpointStatus + (*SandboxProvisioning)(nil), // 263: openshell.v1.SandboxProvisioning + (*SandboxServiceExposure)(nil), // 264: openshell.v1.SandboxServiceExposure + nil, // 265: openshell.v1.SandboxSpec.EnvironmentEntry + nil, // 266: openshell.v1.SandboxTemplate.LabelsEntry + nil, // 267: openshell.v1.SandboxTemplate.AnnotationsEntry + nil, // 268: openshell.v1.SandboxTemplate.EnvironmentEntry + nil, // 269: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + nil, // 270: openshell.v1.PlatformEvent.MetadataEntry + nil, // 271: openshell.v1.CreateSandboxRequest.LabelsEntry + nil, // 272: openshell.v1.CreateSandboxRequest.AnnotationsEntry + nil, // 273: openshell.v1.SandboxResponse.ServiceUrlsEntry + nil, // 274: openshell.v1.ExecSandboxRequest.EnvironmentEntry + nil, // 275: openshell.v1.SandboxLogLine.FieldsEntry + nil, // 276: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + nil, // 277: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + nil, // 278: openshell.v1.ProviderProfile.AnnotationsEntry + nil, // 279: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + nil, // 280: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + nil, // 281: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + nil, // 282: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + nil, // 283: openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry + nil, // 284: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry + nil, // 285: openshell.v1.ProviderEnvironmentSnapshot.FilesEntry + nil, // 286: openshell.v1.UpdateConfigRequest.AnnotationsEntry + nil, // 287: openshell.v1.UpdateConfigResponse.AnnotationsEntry + nil, // 288: openshell.v1.SandboxPolicyRevision.ProvenanceEntry + nil, // 289: openshell.v1.CreateWorkspaceRequest.LabelsEntry + (*timestamppb.Timestamp)(nil), // 290: google.protobuf.Timestamp + (*datamodelv1.ObjectMeta)(nil), // 291: openshell.datamodel.v1.ObjectMeta + (*sandboxv1.SandboxPolicy)(nil), // 292: openshell.sandbox.v1.SandboxPolicy + (*structpb.Struct)(nil), // 293: google.protobuf.Struct + (*durationpb.Duration)(nil), // 294: google.protobuf.Duration + (*datamodelv1.WorkspaceSelector)(nil), // 295: openshell.datamodel.v1.WorkspaceSelector + (*datamodelv1.Provider)(nil), // 296: openshell.datamodel.v1.Provider + (sandboxv1.PolicySource)(0), // 297: openshell.sandbox.v1.PolicySource + (*sandboxv1.NetworkEndpoint)(nil), // 298: openshell.sandbox.v1.NetworkEndpoint + (*sandboxv1.NetworkBinary)(nil), // 299: openshell.sandbox.v1.NetworkBinary + (*sandboxv1.SettingValue)(nil), // 300: openshell.sandbox.v1.SettingValue + (*sandboxv1.NetworkPolicyRule)(nil), // 301: openshell.sandbox.v1.NetworkPolicyRule + (*sandboxv1.L7DenyRule)(nil), // 302: openshell.sandbox.v1.L7DenyRule + (*sandboxv1.L7Rule)(nil), // 303: openshell.sandbox.v1.L7Rule + (*sandboxv1.SandboxConfigSnapshot)(nil), // 304: openshell.sandbox.v1.SandboxConfigSnapshot + (*datamodelv1.Workspace)(nil), // 305: openshell.datamodel.v1.Workspace + (*sandboxv1.GetSandboxConfigRequest)(nil), // 306: openshell.sandbox.v1.GetSandboxConfigRequest + (*sandboxv1.GetGatewayConfigRequest)(nil), // 307: openshell.sandbox.v1.GetGatewayConfigRequest + (*sandboxv1.GetSandboxConfigResponse)(nil), // 308: openshell.sandbox.v1.GetSandboxConfigResponse + (*sandboxv1.GetGatewayConfigResponse)(nil), // 309: openshell.sandbox.v1.GetGatewayConfigResponse } var file_openshell_proto_depIdxs = []int32{ - 289, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp - 289, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp - 257, // 2: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential - 289, // 3: openshell.v1.RefreshSandboxTokenResponse.sandbox_expiration_time:type_name -> google.protobuf.Timestamp + 290, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp + 290, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp + 258, // 2: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential + 290, // 3: openshell.v1.RefreshSandboxTokenResponse.sandbox_expiration_time:type_name -> google.protobuf.Timestamp 14, // 4: openshell.v1.HealthResponse.status:type_name -> openshell.v1.ServiceStatus 14, // 5: openshell.v1.GetGatewayInfoResponse.status:type_name -> openshell.v1.ServiceStatus 32, // 6: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo @@ -20820,115 +20883,115 @@ var file_openshell_proto_depIdxs = []int32{ 35, // 11: openshell.v1.ResourceCapabilities.cpu:type_name -> openshell.v1.CpuResourceCapabilities 36, // 12: openshell.v1.ResourceCapabilities.memory:type_name -> openshell.v1.MemoryResourceCapabilities 37, // 13: openshell.v1.ResourceCapabilities.gpu:type_name -> openshell.v1.GpuResourceCapabilities - 290, // 14: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 291, // 14: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta 39, // 15: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec 50, // 16: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus 49, // 17: openshell.v1.Sandbox.created_from_workload_template:type_name -> openshell.v1.SandboxWorkloadTemplateProvenance - 264, // 18: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry + 265, // 18: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry 42, // 19: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate - 291, // 20: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 292, // 20: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy 40, // 21: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements 17, // 22: openshell.v1.SandboxSpec.restart_policy:type_name -> openshell.v1.SandboxRestartPolicy 41, // 23: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements - 265, // 24: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry - 266, // 25: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry - 267, // 26: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry - 292, // 27: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct - 292, // 28: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct - 290, // 29: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 266, // 24: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry + 267, // 25: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry + 268, // 26: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry + 293, // 27: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct + 293, // 28: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct + 291, // 29: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta 44, // 30: openshell.v1.SandboxWorkloadTemplate.spec:type_name -> openshell.v1.SandboxWorkloadTemplateSpec 45, // 31: openshell.v1.SandboxWorkloadTemplateSpec.workload:type_name -> openshell.v1.SandboxWorkloadConfig - 292, // 32: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct + 293, // 32: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct 47, // 33: openshell.v1.SandboxWorkloadTemplateSpec.desired_service_level:type_name -> openshell.v1.SandboxServiceLevel - 268, // 34: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + 269, // 34: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry 46, // 35: openshell.v1.SandboxWorkloadConfig.resources:type_name -> openshell.v1.SandboxResources 41, // 36: openshell.v1.SandboxResources.gpu:type_name -> openshell.v1.GpuResourceRequirements 48, // 37: openshell.v1.SandboxServiceLevel.startup:type_name -> openshell.v1.SandboxStartup - 293, // 38: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration + 294, // 38: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration 51, // 39: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition 1, // 40: openshell.v1.SandboxStatus.phase:type_name -> openshell.v1.SandboxPhase - 261, // 41: openshell.v1.SandboxStatus.endpoint_statuses:type_name -> openshell.v1.EndpointStatus + 262, // 41: openshell.v1.SandboxStatus.endpoint_statuses:type_name -> openshell.v1.EndpointStatus 179, // 42: openshell.v1.SandboxStatus.configuration_admission:type_name -> openshell.v1.SandboxConfigurationAdmission - 262, // 43: openshell.v1.SandboxStatus.provisioning:type_name -> openshell.v1.SandboxProvisioning - 289, // 44: openshell.v1.SandboxStatus.next_restart_time:type_name -> google.protobuf.Timestamp - 289, // 45: openshell.v1.SandboxStatus.main_process_started_time:type_name -> google.protobuf.Timestamp - 289, // 46: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp - 289, // 47: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp - 269, // 48: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry - 294, // 49: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 263, // 43: openshell.v1.SandboxStatus.provisioning:type_name -> openshell.v1.SandboxProvisioning + 290, // 44: openshell.v1.SandboxStatus.next_restart_time:type_name -> google.protobuf.Timestamp + 290, // 45: openshell.v1.SandboxStatus.main_process_started_time:type_name -> google.protobuf.Timestamp + 290, // 46: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp + 290, // 47: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp + 270, // 48: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry + 295, // 49: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 39, // 50: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec - 270, // 51: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry - 271, // 52: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry - 263, // 53: openshell.v1.CreateSandboxRequest.service_exposures:type_name -> openshell.v1.SandboxServiceExposure - 294, // 54: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 271, // 51: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry + 272, // 52: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry + 264, // 53: openshell.v1.CreateSandboxRequest.service_exposures:type_name -> openshell.v1.SandboxServiceExposure + 295, // 54: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 43, // 55: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate - 294, // 56: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 57: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 58: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 56: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 57: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 58: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 43, // 59: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate 43, // 60: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate 18, // 61: openshell.v1.DeleteSandboxTemplateResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 294, // 62: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 63: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp - 294, // 64: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 65: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 66: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 67: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 68: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 69: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 70: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 71: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 62: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 290, // 63: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp + 295, // 64: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 65: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 66: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 67: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 68: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 69: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 70: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 71: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 38, // 72: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox - 272, // 73: openshell.v1.SandboxResponse.service_urls:type_name -> openshell.v1.SandboxResponse.ServiceUrlsEntry + 273, // 73: openshell.v1.SandboxResponse.service_urls:type_name -> openshell.v1.SandboxResponse.ServiceUrlsEntry 38, // 74: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox - 295, // 75: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 296, // 75: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider 38, // 76: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox 80, // 77: openshell.v1.AttachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt 38, // 78: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox 80, // 79: openshell.v1.DetachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt 78, // 80: openshell.v1.ConfigSnapshotRevision.sandbox_config:type_name -> openshell.v1.SandboxConfigRevision 76, // 81: openshell.v1.ConfigSnapshotRevision.provider_target:type_name -> openshell.v1.ProviderDesiredIdentity - 296, // 82: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource + 297, // 82: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource 5, // 83: openshell.v1.ConfigUpdateOperation.component:type_name -> openshell.v1.ConfigComponent 77, // 84: openshell.v1.ConfigUpdateOperation.target_revision:type_name -> openshell.v1.ConfigSnapshotRevision 7, // 85: openshell.v1.ConfigUpdateOperation.state:type_name -> openshell.v1.ConfigUpdateOperationState 6, // 86: openshell.v1.ConfigUpdateOperation.outcome:type_name -> openshell.v1.ConfigApplyOutcome - 289, // 87: openshell.v1.ConfigUpdateOperation.created_time:type_name -> google.protobuf.Timestamp - 289, // 88: openshell.v1.ConfigUpdateOperation.updated_time:type_name -> google.protobuf.Timestamp - 289, // 89: openshell.v1.ConfigUpdateOperation.completed_time:type_name -> google.protobuf.Timestamp + 290, // 87: openshell.v1.ConfigUpdateOperation.created_time:type_name -> google.protobuf.Timestamp + 290, // 88: openshell.v1.ConfigUpdateOperation.updated_time:type_name -> google.protobuf.Timestamp + 290, // 89: openshell.v1.ConfigUpdateOperation.completed_time:type_name -> google.protobuf.Timestamp 2, // 90: openshell.v1.ProviderMutationReceipt.kind:type_name -> openshell.v1.ProviderMutationKind 76, // 91: openshell.v1.ProviderMutationReceipt.desired:type_name -> openshell.v1.ProviderDesiredIdentity - 289, // 92: openshell.v1.ProviderMutationReceipt.persisted_time:type_name -> google.protobuf.Timestamp + 290, // 92: openshell.v1.ProviderMutationReceipt.persisted_time:type_name -> google.protobuf.Timestamp 4, // 93: openshell.v1.ProviderReadinessObservation.reason:type_name -> openshell.v1.ProviderReadinessReason 80, // 94: openshell.v1.ProviderReadinessStatus.receipt:type_name -> openshell.v1.ProviderMutationReceipt 3, // 95: openshell.v1.ProviderReadinessStatus.state:type_name -> openshell.v1.ProviderReadinessState 4, // 96: openshell.v1.ProviderReadinessStatus.reason:type_name -> openshell.v1.ProviderReadinessReason 81, // 97: openshell.v1.ProviderReadinessStatus.observed:type_name -> openshell.v1.ProviderReadinessObservation - 289, // 98: openshell.v1.ProviderReadinessStatus.observed_time:type_name -> google.protobuf.Timestamp - 289, // 99: openshell.v1.ProviderReadinessStatus.evaluated_time:type_name -> google.protobuf.Timestamp + 290, // 98: openshell.v1.ProviderReadinessStatus.observed_time:type_name -> google.protobuf.Timestamp + 290, // 99: openshell.v1.ProviderReadinessStatus.evaluated_time:type_name -> google.protobuf.Timestamp 79, // 100: openshell.v1.ProviderReadinessStatus.operation:type_name -> openshell.v1.ConfigUpdateOperation - 294, // 101: openshell.v1.GetSandboxProviderStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 101: openshell.v1.GetSandboxProviderStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 82, // 102: openshell.v1.GetSandboxProviderStatusResponse.status:type_name -> openshell.v1.ProviderReadinessStatus 81, // 103: openshell.v1.ReportProviderReadinessRequest.observation:type_name -> openshell.v1.ProviderReadinessObservation - 293, // 104: openshell.v1.ReportProviderReadinessResponse.report_interval:type_name -> google.protobuf.Duration - 293, // 105: openshell.v1.ReportProviderReadinessResponse.observation_ttl:type_name -> google.protobuf.Duration + 294, // 104: openshell.v1.ReportProviderReadinessResponse.report_interval:type_name -> google.protobuf.Duration + 294, // 105: openshell.v1.ReportProviderReadinessResponse.observation_ttl:type_name -> google.protobuf.Duration 18, // 106: openshell.v1.DeleteSandboxResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 294, // 107: openshell.v1.CreateSshSessionRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 108: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp - 294, // 109: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 107: openshell.v1.CreateSshSessionRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 290, // 108: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp + 295, // 109: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 20, // 110: openshell.v1.ExposeServiceRequest.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode - 294, // 111: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 112: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 111: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 112: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 97, // 113: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse - 294, // 114: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 114: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 18, // 115: openshell.v1.DeleteServiceResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 290, // 116: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 291, // 116: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta 20, // 117: openshell.v1.ServiceEndpoint.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode 96, // 118: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint 18, // 119: openshell.v1.RevokeSshSessionResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 294, // 120: openshell.v1.ExecSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 273, // 121: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry - 293, // 122: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration + 295, // 120: openshell.v1.ExecSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 274, // 121: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry + 294, // 122: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration 101, // 123: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout 102, // 124: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr 103, // 125: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit @@ -20937,148 +21000,148 @@ var file_openshell_proto_depIdxs = []int32{ 105, // 128: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit 100, // 129: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest 108, // 130: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize - 290, // 131: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 289, // 132: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp - 294, // 133: openshell.v1.WatchSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 134: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp + 291, // 131: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 290, // 132: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp + 295, // 133: openshell.v1.WatchSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 290, // 134: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp 38, // 135: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox 112, // 136: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine 52, // 137: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent 113, // 138: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning - 221, // 139: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate - 289, // 140: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp - 274, // 141: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry - 294, // 142: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 295, // 143: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 294, // 144: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 145: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 146: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 295, // 147: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 275, // 148: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - 294, // 149: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 295, // 150: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider + 222, // 139: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate + 290, // 140: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp + 275, // 141: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry + 295, // 142: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 296, // 143: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 295, // 144: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 145: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 146: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 296, // 147: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 276, // 148: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + 295, // 149: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 296, // 150: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider 80, // 151: openshell.v1.ProviderResponse.target_receipts:type_name -> openshell.v1.ProviderMutationReceipt - 295, // 152: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 294, // 153: openshell.v1.ListProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 154: openshell.v1.GetProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 296, // 152: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 295, // 153: openshell.v1.ListProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 154: openshell.v1.GetProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 142, // 155: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile - 293, // 156: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration + 294, // 156: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration 125, // 157: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride 8, // 158: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType 126, // 159: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken 131, // 160: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh 127, // 161: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant 9, // 162: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 293, // 163: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration - 293, // 164: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration + 294, // 163: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration + 294, // 164: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration 129, // 165: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial 130, // 166: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput 9, // 167: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 289, // 168: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp - 289, // 169: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp - 289, // 170: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp + 290, // 168: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp + 290, // 169: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp + 290, // 170: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp 16, // 171: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 289, // 172: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp - 294, // 173: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 290, // 172: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp + 295, // 173: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 132, // 174: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 294, // 175: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 175: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 9, // 176: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 276, // 177: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - 289, // 178: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp + 277, // 177: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + 290, // 178: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp 132, // 179: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 294, // 180: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 180: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 132, // 181: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 294, // 182: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 182: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 18, // 183: openshell.v1.DeleteProviderRefreshResponse.outcome:type_name -> openshell.v1.DeletionOutcome 10, // 184: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory 128, // 185: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential - 297, // 186: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint - 298, // 187: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 298, // 186: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 299, // 187: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary 133, // 188: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery - 277, // 189: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry + 278, // 189: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry 143, // 190: openshell.v1.ProviderProfile.files:type_name -> openshell.v1.ProviderProfileFile 142, // 191: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile 142, // 192: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 294, // 193: openshell.v1.ImportProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 193: openshell.v1.ImportProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 123, // 194: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem 124, // 195: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic 142, // 196: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 294, // 197: openshell.v1.UpdateProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 197: openshell.v1.UpdateProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 123, // 198: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem 124, // 199: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic 142, // 200: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile - 294, // 201: openshell.v1.LintProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 201: openshell.v1.LintProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 123, // 202: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem 124, // 203: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic 18, // 204: openshell.v1.DeleteProviderResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 294, // 205: openshell.v1.DeleteProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 205: openshell.v1.DeleteProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 18, // 206: openshell.v1.DeleteProviderProfileResponse.outcome:type_name -> openshell.v1.DeletionOutcome 156, // 207: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding - 278, // 208: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - 279, // 209: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - 280, // 210: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - 281, // 211: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + 279, // 208: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + 280, // 209: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + 281, // 210: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + 282, // 211: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry 4, // 212: openshell.v1.GetSandboxProviderEnvironmentResponse.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason - 282, // 213: openshell.v1.GetSandboxProviderEnvironmentResponse.files:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry + 283, // 213: openshell.v1.GetSandboxProviderEnvironmentResponse.files:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry 11, // 214: openshell.v1.ProviderEnvironmentValue.classification:type_name -> openshell.v1.ProviderEnvironmentValueClassification 157, // 215: openshell.v1.ProviderEnvironmentValue.static_credential_binding:type_name -> openshell.v1.StaticCredentialBinding - 289, // 216: openshell.v1.ProviderEnvironmentValue.expiration_time:type_name -> google.protobuf.Timestamp + 290, // 216: openshell.v1.ProviderEnvironmentValue.expiration_time:type_name -> google.protobuf.Timestamp 159, // 217: openshell.v1.ProviderEnvironmentSnapshot.values:type_name -> openshell.v1.ProviderEnvironmentValue - 283, // 218: openshell.v1.ProviderEnvironmentSnapshot.dynamic_credentials:type_name -> openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry + 284, // 218: openshell.v1.ProviderEnvironmentSnapshot.dynamic_credentials:type_name -> openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry 4, // 219: openshell.v1.ProviderEnvironmentSnapshot.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason - 284, // 220: openshell.v1.ProviderEnvironmentSnapshot.files:type_name -> openshell.v1.ProviderEnvironmentSnapshot.FilesEntry - 293, // 221: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration - 294, // 222: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 291, // 223: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 299, // 224: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue + 285, // 220: openshell.v1.ProviderEnvironmentSnapshot.files:type_name -> openshell.v1.ProviderEnvironmentSnapshot.FilesEntry + 294, // 221: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration + 295, // 222: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 292, // 223: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 300, // 224: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue 164, // 225: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation - 285, // 226: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry + 286, // 226: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry 165, // 227: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule 166, // 228: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint 167, // 229: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule 169, // 230: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules 170, // 231: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules 171, // 232: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary - 300, // 233: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 298, // 234: openshell.v1.L7RuleTarget.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 301, // 235: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 301, // 233: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 299, // 234: openshell.v1.L7RuleTarget.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 302, // 235: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule 168, // 236: openshell.v1.AddDenyRules.target:type_name -> openshell.v1.L7RuleTarget - 302, // 237: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule + 303, // 237: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule 168, // 238: openshell.v1.AddAllowRules.target:type_name -> openshell.v1.L7RuleTarget - 286, // 239: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry - 294, // 240: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 287, // 239: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry + 295, // 240: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 182, // 241: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision - 294, // 242: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 242: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 182, // 243: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision 13, // 244: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus 12, // 245: openshell.v1.SandboxConfigurationAdmission.state:type_name -> openshell.v1.ConfigurationAdmissionState 179, // 246: openshell.v1.ReportSandboxConfigurationRequest.admission:type_name -> openshell.v1.SandboxConfigurationAdmission 13, // 247: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus - 289, // 248: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp - 289, // 249: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp - 291, // 250: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 287, // 251: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry - 294, // 252: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 253: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp + 290, // 248: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp + 290, // 249: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp + 292, // 250: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 288, // 251: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry + 295, // 252: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 290, // 253: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp 112, // 254: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine 112, // 255: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine 189, // 256: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello 198, // 257: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat - 214, // 258: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult - 215, // 259: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose + 215, // 258: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult + 216, // 259: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose 195, // 260: openshell.v1.SupervisorMessage.config_update_result:type_name -> openshell.v1.ConfigUpdateResult 196, // 261: openshell.v1.SupervisorMessage.config_bootstrap_result:type_name -> openshell.v1.ConfigBootstrapResult 190, // 262: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted 197, // 263: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected 199, // 264: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat 204, // 265: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen - 215, // 266: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose + 216, // 266: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose 192, // 267: openshell.v1.GatewayMessage.config_update:type_name -> openshell.v1.ConfigUpdate 191, // 268: openshell.v1.SessionAccepted.bootstrap:type_name -> openshell.v1.ConfigBootstrap - 293, // 269: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration - 303, // 270: openshell.v1.ConfigBootstrap.sandbox_config:type_name -> openshell.sandbox.v1.SandboxConfigSnapshot + 294, // 269: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration + 304, // 270: openshell.v1.ConfigBootstrap.sandbox_config:type_name -> openshell.sandbox.v1.SandboxConfigSnapshot 160, // 271: openshell.v1.ConfigBootstrap.provider_environment:type_name -> openshell.v1.ProviderEnvironmentSnapshot - 303, // 272: openshell.v1.ConfigUpdate.sandbox_config:type_name -> openshell.sandbox.v1.SandboxConfigSnapshot + 304, // 272: openshell.v1.ConfigUpdate.sandbox_config:type_name -> openshell.sandbox.v1.SandboxConfigSnapshot 160, // 273: openshell.v1.ConfigUpdate.provider_environment:type_name -> openshell.v1.ProviderEnvironmentSnapshot 5, // 274: openshell.v1.ConfigComponentApplyResult.component:type_name -> openshell.v1.ConfigComponent 77, // 275: openshell.v1.ConfigComponentApplyResult.requested_revision:type_name -> openshell.v1.ConfigSnapshotRevision @@ -21093,238 +21156,239 @@ var file_openshell_proto_depIdxs = []int32{ 204, // 284: openshell.v1.PeerRelayInit.relay_open:type_name -> openshell.v1.RelayOpen 209, // 285: openshell.v1.PeerRelayFrame.init:type_name -> openshell.v1.PeerRelayInit 211, // 286: openshell.v1.PeerConfigUpdateHintRequest.sandbox:type_name -> openshell.v1.PeerConfigSandboxTarget - 289, // 287: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp - 289, // 288: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp - 216, // 289: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample - 218, // 290: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount - 300, // 291: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 289, // 292: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp - 289, // 293: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp - 289, // 294: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp - 289, // 295: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp - 291, // 296: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 291, // 297: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 294, // 298: openshell.v1.SubmitPolicyAnalysisRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 217, // 299: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary - 220, // 300: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk - 219, // 301: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary - 294, // 302: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 220, // 303: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk - 289, // 304: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp - 294, // 305: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 306: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 307: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 230, // 308: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval - 294, // 309: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 300, // 310: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 294, // 311: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 312: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 294, // 313: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 314: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp - 240, // 315: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry - 288, // 316: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry - 304, // 317: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 304, // 318: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 304, // 319: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace - 18, // 320: openshell.v1.DeleteWorkspaceResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 290, // 321: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 15, // 322: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole - 294, // 323: openshell.v1.AddWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 15, // 324: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole - 250, // 325: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember - 294, // 326: openshell.v1.RemoveWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 18, // 327: openshell.v1.RemoveWorkspaceMemberResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 294, // 328: openshell.v1.ListWorkspaceMembersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 250, // 329: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember - 289, // 330: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp - 19, // 331: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult - 258, // 332: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation - 19, // 333: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult - 289, // 334: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp - 289, // 335: openshell.v1.SandboxProvisioning.configuration_change_time:type_name -> google.protobuf.Timestamp - 289, // 336: openshell.v1.SandboxProvisioning.first_rejection_time:type_name -> google.protobuf.Timestamp - 289, // 337: openshell.v1.SandboxProvisioning.deadline:type_name -> google.protobuf.Timestamp - 289, // 338: openshell.v1.SandboxProvisioning.timeout_time:type_name -> google.protobuf.Timestamp - 289, // 339: openshell.v1.SandboxProvisioning.cleanup_completed_time:type_name -> google.protobuf.Timestamp - 289, // 340: openshell.v1.SandboxProvisioning.cleanup_retry_time:type_name -> google.protobuf.Timestamp - 289, // 341: openshell.v1.SandboxProvisioning.attachment_change_time:type_name -> google.protobuf.Timestamp - 20, // 342: openshell.v1.SandboxServiceExposure.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode - 289, // 343: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 289, // 344: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 128, // 345: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 157, // 346: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding - 128, // 347: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 25, // 348: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest - 27, // 349: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest - 29, // 350: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest - 53, // 351: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest - 61, // 352: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest - 63, // 353: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest - 64, // 354: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest - 54, // 355: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest - 55, // 356: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest - 56, // 357: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest - 57, // 358: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest - 65, // 359: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest - 66, // 360: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest - 67, // 361: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest - 83, // 362: openshell.v1.OpenShell.GetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest - 68, // 363: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest - 69, // 364: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest - 70, // 365: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest - 88, // 366: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest - 90, // 367: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest - 91, // 368: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest - 92, // 369: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest - 94, // 370: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest - 98, // 371: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest - 100, // 372: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest - 106, // 373: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame - 107, // 374: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput - 114, // 375: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest - 115, // 376: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest - 116, // 377: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest - 121, // 378: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest - 122, // 379: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest - 146, // 380: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest - 148, // 381: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest - 150, // 382: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest - 117, // 383: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest - 134, // 384: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest - 136, // 385: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest - 138, // 386: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest - 140, // 387: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest - 118, // 388: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest - 153, // 389: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest - 305, // 390: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest - 306, // 391: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest - 163, // 392: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest - 173, // 393: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest - 175, // 394: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest - 177, // 395: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest - 259, // 396: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest - 85, // 397: openshell.v1.OpenShell.ReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest - 180, // 398: openshell.v1.OpenShell.ReportSandboxConfiguration:input_type -> openshell.v1.ReportSandboxConfigurationRequest - 155, // 399: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest - 161, // 400: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest - 183, // 401: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest - 184, // 402: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest - 187, // 403: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage - 200, // 404: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest - 202, // 405: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest - 208, // 406: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame - 210, // 407: openshell.v1.OpenShell.PeerRelay:input_type -> openshell.v1.PeerRelayFrame - 85, // 408: openshell.v1.OpenShell.PeerReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest - 259, // 409: openshell.v1.OpenShell.PeerReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest - 83, // 410: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest - 212, // 411: openshell.v1.OpenShell.PeerNotifyConfigUpdate:input_type -> openshell.v1.PeerConfigUpdateHintRequest - 110, // 412: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest - 222, // 413: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest - 224, // 414: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest - 226, // 415: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest - 228, // 416: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest - 231, // 417: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest - 233, // 418: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest - 235, // 419: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest - 237, // 420: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest - 239, // 421: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest - 21, // 422: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest - 23, // 423: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest - 242, // 424: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest - 244, // 425: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest - 246, // 426: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest - 248, // 427: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest - 251, // 428: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest - 253, // 429: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest - 255, // 430: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest - 26, // 431: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse - 28, // 432: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse - 30, // 433: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse - 71, // 434: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse - 62, // 435: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse - 71, // 436: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse - 72, // 437: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse - 58, // 438: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 58, // 439: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 59, // 440: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse - 60, // 441: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse - 73, // 442: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse - 74, // 443: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse - 75, // 444: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse - 84, // 445: openshell.v1.OpenShell.GetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse - 87, // 446: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse - 71, // 447: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse - 71, // 448: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse - 89, // 449: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse - 97, // 450: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse - 97, // 451: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse - 93, // 452: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse - 95, // 453: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse - 99, // 454: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse - 104, // 455: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent - 106, // 456: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame - 104, // 457: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent - 119, // 458: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse - 119, // 459: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse - 120, // 460: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse - 145, // 461: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse - 144, // 462: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse - 147, // 463: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse - 149, // 464: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse - 151, // 465: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse - 119, // 466: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse - 135, // 467: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse - 137, // 468: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse - 139, // 469: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse - 141, // 470: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse - 152, // 471: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse - 154, // 472: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse - 307, // 473: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse - 308, // 474: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse - 172, // 475: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse - 174, // 476: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse - 176, // 477: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse - 178, // 478: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse - 260, // 479: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse - 86, // 480: openshell.v1.OpenShell.ReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse - 181, // 481: openshell.v1.OpenShell.ReportSandboxConfiguration:output_type -> openshell.v1.ReportSandboxConfigurationResponse - 158, // 482: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse - 162, // 483: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse - 186, // 484: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse - 185, // 485: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse - 188, // 486: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage - 201, // 487: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse - 203, // 488: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse - 208, // 489: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame - 210, // 490: openshell.v1.OpenShell.PeerRelay:output_type -> openshell.v1.PeerRelayFrame - 86, // 491: openshell.v1.OpenShell.PeerReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse - 260, // 492: openshell.v1.OpenShell.PeerReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse - 84, // 493: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse - 213, // 494: openshell.v1.OpenShell.PeerNotifyConfigUpdate:output_type -> openshell.v1.PeerConfigUpdateHintResponse - 111, // 495: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent - 223, // 496: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse - 225, // 497: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse - 227, // 498: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse - 229, // 499: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse - 232, // 500: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse - 234, // 501: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse - 236, // 502: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse - 238, // 503: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse - 241, // 504: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse - 22, // 505: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse - 24, // 506: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse - 243, // 507: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse - 245, // 508: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse - 247, // 509: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse - 249, // 510: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse - 252, // 511: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse - 254, // 512: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse - 256, // 513: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse - 431, // [431:514] is the sub-list for method output_type - 348, // [348:431] is the sub-list for method input_type - 348, // [348:348] is the sub-list for extension type_name - 348, // [348:348] is the sub-list for extension extendee - 0, // [0:348] is the sub-list for field type_name + 212, // 287: openshell.v1.PeerConfigUpdateHintRequest.provider:type_name -> openshell.v1.PeerConfigProviderTarget + 290, // 288: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp + 290, // 289: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp + 217, // 290: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample + 219, // 291: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount + 301, // 292: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 290, // 293: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp + 290, // 294: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp + 290, // 295: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp + 290, // 296: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp + 292, // 297: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 292, // 298: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 295, // 299: openshell.v1.SubmitPolicyAnalysisRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 218, // 300: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary + 221, // 301: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk + 220, // 302: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary + 295, // 303: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 221, // 304: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk + 290, // 305: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp + 295, // 306: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 307: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 308: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 231, // 309: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval + 295, // 310: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 301, // 311: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 295, // 312: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 313: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 295, // 314: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 290, // 315: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp + 241, // 316: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry + 289, // 317: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry + 305, // 318: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 305, // 319: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 305, // 320: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace + 18, // 321: openshell.v1.DeleteWorkspaceResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 291, // 322: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 15, // 323: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole + 295, // 324: openshell.v1.AddWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 15, // 325: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole + 251, // 326: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember + 295, // 327: openshell.v1.RemoveWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 18, // 328: openshell.v1.RemoveWorkspaceMemberResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 295, // 329: openshell.v1.ListWorkspaceMembersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 251, // 330: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember + 290, // 331: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp + 19, // 332: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult + 259, // 333: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation + 19, // 334: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult + 290, // 335: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp + 290, // 336: openshell.v1.SandboxProvisioning.configuration_change_time:type_name -> google.protobuf.Timestamp + 290, // 337: openshell.v1.SandboxProvisioning.first_rejection_time:type_name -> google.protobuf.Timestamp + 290, // 338: openshell.v1.SandboxProvisioning.deadline:type_name -> google.protobuf.Timestamp + 290, // 339: openshell.v1.SandboxProvisioning.timeout_time:type_name -> google.protobuf.Timestamp + 290, // 340: openshell.v1.SandboxProvisioning.cleanup_completed_time:type_name -> google.protobuf.Timestamp + 290, // 341: openshell.v1.SandboxProvisioning.cleanup_retry_time:type_name -> google.protobuf.Timestamp + 290, // 342: openshell.v1.SandboxProvisioning.attachment_change_time:type_name -> google.protobuf.Timestamp + 20, // 343: openshell.v1.SandboxServiceExposure.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode + 290, // 344: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 290, // 345: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 128, // 346: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 157, // 347: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding + 128, // 348: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 25, // 349: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest + 27, // 350: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest + 29, // 351: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest + 53, // 352: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest + 61, // 353: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest + 63, // 354: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest + 64, // 355: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest + 54, // 356: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest + 55, // 357: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest + 56, // 358: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest + 57, // 359: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest + 65, // 360: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest + 66, // 361: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest + 67, // 362: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest + 83, // 363: openshell.v1.OpenShell.GetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest + 68, // 364: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest + 69, // 365: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest + 70, // 366: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest + 88, // 367: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest + 90, // 368: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest + 91, // 369: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest + 92, // 370: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest + 94, // 371: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest + 98, // 372: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest + 100, // 373: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest + 106, // 374: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame + 107, // 375: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput + 114, // 376: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest + 115, // 377: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest + 116, // 378: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest + 121, // 379: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest + 122, // 380: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest + 146, // 381: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest + 148, // 382: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest + 150, // 383: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest + 117, // 384: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest + 134, // 385: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest + 136, // 386: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest + 138, // 387: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest + 140, // 388: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest + 118, // 389: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest + 153, // 390: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest + 306, // 391: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest + 307, // 392: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest + 163, // 393: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest + 173, // 394: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest + 175, // 395: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest + 177, // 396: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest + 260, // 397: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest + 85, // 398: openshell.v1.OpenShell.ReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest + 180, // 399: openshell.v1.OpenShell.ReportSandboxConfiguration:input_type -> openshell.v1.ReportSandboxConfigurationRequest + 155, // 400: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest + 161, // 401: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest + 183, // 402: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest + 184, // 403: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest + 187, // 404: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage + 200, // 405: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest + 202, // 406: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest + 208, // 407: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame + 210, // 408: openshell.v1.OpenShell.PeerRelay:input_type -> openshell.v1.PeerRelayFrame + 85, // 409: openshell.v1.OpenShell.PeerReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest + 260, // 410: openshell.v1.OpenShell.PeerReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest + 83, // 411: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest + 213, // 412: openshell.v1.OpenShell.PeerNotifyConfigUpdate:input_type -> openshell.v1.PeerConfigUpdateHintRequest + 110, // 413: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest + 223, // 414: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest + 225, // 415: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest + 227, // 416: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest + 229, // 417: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest + 232, // 418: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest + 234, // 419: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest + 236, // 420: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest + 238, // 421: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest + 240, // 422: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest + 21, // 423: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest + 23, // 424: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest + 243, // 425: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest + 245, // 426: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest + 247, // 427: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest + 249, // 428: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest + 252, // 429: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest + 254, // 430: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest + 256, // 431: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest + 26, // 432: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse + 28, // 433: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse + 30, // 434: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse + 71, // 435: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse + 62, // 436: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse + 71, // 437: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse + 72, // 438: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse + 58, // 439: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 58, // 440: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 59, // 441: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse + 60, // 442: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse + 73, // 443: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse + 74, // 444: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse + 75, // 445: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse + 84, // 446: openshell.v1.OpenShell.GetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse + 87, // 447: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse + 71, // 448: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse + 71, // 449: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse + 89, // 450: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse + 97, // 451: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse + 97, // 452: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse + 93, // 453: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse + 95, // 454: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse + 99, // 455: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse + 104, // 456: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent + 106, // 457: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame + 104, // 458: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent + 119, // 459: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse + 119, // 460: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse + 120, // 461: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse + 145, // 462: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse + 144, // 463: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse + 147, // 464: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse + 149, // 465: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse + 151, // 466: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse + 119, // 467: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse + 135, // 468: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse + 137, // 469: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse + 139, // 470: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse + 141, // 471: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse + 152, // 472: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse + 154, // 473: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse + 308, // 474: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse + 309, // 475: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse + 172, // 476: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse + 174, // 477: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse + 176, // 478: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse + 178, // 479: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse + 261, // 480: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse + 86, // 481: openshell.v1.OpenShell.ReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse + 181, // 482: openshell.v1.OpenShell.ReportSandboxConfiguration:output_type -> openshell.v1.ReportSandboxConfigurationResponse + 158, // 483: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse + 162, // 484: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse + 186, // 485: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse + 185, // 486: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse + 188, // 487: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage + 201, // 488: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse + 203, // 489: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse + 208, // 490: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame + 210, // 491: openshell.v1.OpenShell.PeerRelay:output_type -> openshell.v1.PeerRelayFrame + 86, // 492: openshell.v1.OpenShell.PeerReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse + 261, // 493: openshell.v1.OpenShell.PeerReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse + 84, // 494: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse + 214, // 495: openshell.v1.OpenShell.PeerNotifyConfigUpdate:output_type -> openshell.v1.PeerConfigUpdateHintResponse + 111, // 496: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent + 224, // 497: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse + 226, // 498: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse + 228, // 499: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse + 230, // 500: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse + 233, // 501: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse + 235, // 502: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse + 237, // 503: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse + 239, // 504: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse + 242, // 505: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse + 22, // 506: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse + 24, // 507: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse + 244, // 508: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse + 246, // 509: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse + 248, // 510: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse + 250, // 511: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse + 253, // 512: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse + 255, // 513: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse + 257, // 514: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse + 432, // [432:515] is the sub-list for method output_type + 349, // [349:432] is the sub-list for method input_type + 349, // [349:349] is the sub-list for extension type_name + 349, // [349:349] is the sub-list for extension extendee + 0, // [0:349] is the sub-list for field type_name } func init() { file_openshell_proto_init() } @@ -21406,10 +21470,11 @@ func file_openshell_proto_init() { (*PeerRelayFrame_Init)(nil), (*PeerRelayFrame_Data)(nil), } - file_openshell_proto_msgTypes[191].OneofWrappers = []any{ + file_openshell_proto_msgTypes[192].OneofWrappers = []any{ (*PeerConfigUpdateHintRequest_Sandbox)(nil), (*PeerConfigUpdateHintRequest_Workspace)(nil), (*PeerConfigUpdateHintRequest_AllConnected)(nil), + (*PeerConfigUpdateHintRequest_Provider)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -21417,7 +21482,7 @@ func file_openshell_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_openshell_proto_rawDesc), len(file_openshell_proto_rawDesc)), NumEnums: 21, - NumMessages: 268, + NumMessages: 269, NumExtensions: 0, NumServices: 1, },