diff --git a/.audit/stage-3-logical-transaction.tsv b/.audit/stage-3-logical-transaction.tsv new file mode 100644 index 0000000000..2d9641458a --- /dev/null +++ b/.audit/stage-3-logical-transaction.tsv @@ -0,0 +1,21 @@ +ts phase decision why evidence result +2026-09-11T04:06:40Z framing based the implementation branch on the exact Stage 2 PR head the stack must contain only Stage 3 changes PR #3265 head 797c36ebafad2e8b7a4eb59b971e4d27aff6745f branch 1731-durable-config-operations/pimlock created +2026-09-11T04:43:47Z design used an exact policy-version and settings-revision tuple for each operation a result must not complete a different desired state after concurrent mutations crates/openshell-server/src/config_update_operation.rs exact tuple comparison implemented +2026-09-11T04:43:47Z durability stored desired state and its operation in one database transaction a crash after commit must leave enough durable state to redispatch crates/openshell-server/src/persistence/postgres.rs, crates/openshell-server/src/persistence/sqlite.rs atomic policy and settings paths implemented +2026-09-11T04:43:47Z migration removed runtime polling and required supervisor protocol revision 3 all supported peers now consume complete stream bootstrap and updates crates/openshell-sandbox/src/lib.rs, crates/openshell-core/src/proto/mod.rs polling fallback removed and old revisions rejected +2026-09-11T04:43:47Z api made wait consistency server-owned and queryable by operation ID client deadlines must not determine or erase the durable business outcome proto/openshell.proto, crates/openshell-cli/src/run.rs CLI and SDK requests use WAIT_FOR_APPLY +2026-09-11T22:01:00Z atomicity included setting annotations and expected sandbox resource-version CAS in the settings transaction a post-commit annotation failure could otherwise report failure after desired state and its operation committed crates/openshell-server/src/persistence/postgres.rs, crates/openshell-server/src/persistence/sqlite.rs settings, sandbox projection, and operation now commit or roll back together +2026-09-11T22:04:00Z verification validated curated TypeScript and Go SDK contracts public clients must compile and exercise the new consistency and operation fields mise run sdk:ts:ci; mise run go:ci both SDK CI suites passed +2026-09-11T22:05:00Z design-artifact published the state and flow design as a secret gist the implementation and operational contract need a durable reviewable reference https://gist.github.com/pimlock/73aa746b48d82d84001cf8e0afa56bf6 gist published +2026-09-11T22:24:00Z migration removed the remaining Stage 2 compatibility branches and stale polling language protocol revision 3 requires bootstrap and must expose one stream-only configuration path crates/openshell-server/src/supervisor_session.rs, crates/openshell-sandbox/src/lib.rs, docs/ dead optional-bootstrap and fetch-fallback paths removed +2026-09-11T22:25:00Z verification reran the focused custom-image end-to-end suite after one transient assertion failure the first run logged the correct runtime rejection but the client observed terminal state before the detailed condition OPENSHELL_E2E_DOCKER_TEST=custom_image mise run e2e:rust all 3 focused tests passed without changing the assertion +2026-09-11T22:26:00Z self-audit checked for an available session transcript before final review the decision-trail protocol calls for comparison when a transcript artifact exists find /home/pmlocek/.codex -type f -path '*/agent-transcripts/*' no transcript artifact was available; reviewed the trail against the active request and implementation diff +2026-09-11T22:32:00Z verification isolated a full-CI timeout in an unrelated WebSocket compression test the test exercises supervisor-network frame handling and has no configuration-operation dependency mise run ci; cargo test -p openshell-supervisor-network --lib proxy::tests::plaintext_websocket_middleware_inspects_compressed_ws_messages -- --exact --nocapture full run timed out once; isolated rerun passed 1/1 in 8.44 seconds +2026-09-11T22:34:00Z compatibility made protocol revision 3 an exact-match release boundary before removing polling Stage 2 already supplies complete bootstrap, live stream payloads, acknowledgements, and owner reconciliation; exact matching excludes old peers instead of silently degrading PR #3265; proto/sandbox.proto; crates/openshell-server/src/supervisor_session.rs; focused Docker E2E with stream-only runtime current gateway and supervisor interoperate; older revisions are rejected and require coordinated upgrade +2026-09-11T22:34:00Z lifecycle classified phases from authoritative persisted state rather than session presence Ready and transitional phases may have or regain a live runtime, while Stopped and Completed do not; Deleting invalidates further application crates/openshell-server/src/config_update_operation.rs authoritative_phase_classification_is_explicit Stopped and Completed become inactive; Deleting becomes cancelled; Ready, Provisioning, Starting, Stopping, Error, Unknown, and Unspecified stay pending; the current SandboxPhase enum has no Suspended variant +2026-09-11T22:34:00Z completion mapped exact correlated results without treating newer application as proof of older application a degraded result means the requested snapshot was installed with declared degraded middleware behavior, while a retained local override means the requested gateway policy did not become effective crates/openshell-server/src/config_update_operation.rs complete_from_apply_result and target_relation Applied, duplicate, and degraded complete applied; stale completes superseded; retained override and apply failures complete failed; later desired tuples supersede pending older tuples +2026-09-11T22:34:00Z recovery used durable scans, CAS transition claims, and bounded exponential redispatch backoff request memory and request-gateway ownership cannot be correctness dependencies, and concurrent reconcilers must not create unbounded duplicate work crates/openshell-server/src/config_update_operation.rs spawn_reconciler, mutate_record, reconcile_one pending records survive restart, any gateway sharing the store may claim them, and retries rebuild the current complete snapshot +2026-09-11T22:34:00Z api-scope scoped idempotency to sandbox and key, omitted operations for semantic no-ops, bounded server waits, and kept global mutations commit-only retries need stable identity without creating artificial revisions; unbounded fleet fan-out needs a separate operation model proto/openshell.proto; crates/openshell-server/src/grpc/policy.rs; crates/openshell-server/src/config_update_operation.rs retries resolve the stored operation, no-op calls return without an operation, WAIT_FOR_APPLY is rejected globally, and waits default to 60 seconds with a one-hour cap +2026-09-11T22:34:00Z verification-scope mapped Stage 3 recovery requirements to implemented tests and durable invariants the review trail must distinguish executable coverage from properties established by shared persistence and CAS server operation tests, stopped_sandbox_setting_update_commits_with_inactive_operation, supervisor stream tests, SDK CI, Docker E2E inactive, exact tuple ordering, idempotent retry, atomic persistence, stream bootstrap/apply, and polling removal are directly tested; process-kill crash injection and multi-process cross-gateway completion are not dedicated scenarios and remain coverage follow-ups +2026-09-11T22:34:00Z independent-review addressed the cross-model decision-trail audit the reviewer identified omitted lifecycle, compatibility, recovery, API-scope, completion, and verification rationale gpt-6-astra review of .audit/stage-3-logical-transaction.tsv six missing rationale areas added; no code defect was reported by this trail-only audit +2026-09-11T22:37:00Z verification reran the repository full CI gate after isolating the timing failure a green complete rerun removes ambiguity about the final tree mise run ci full CI passed, including 1492 server tests and all configured SDK, lint, policy, packaging, and documentation checks diff --git a/architecture/compute-runtimes.md b/architecture/compute-runtimes.md index 3018e6447d..d892d0a89c 100644 --- a/architecture/compute-runtimes.md +++ b/architecture/compute-runtimes.md @@ -433,7 +433,7 @@ process-supervision leaf and launches the user workload after the sidecar serves bootstrap state over a local control socket. The network sidecar owns gateway credentials and sends policy plus workload-facing provider environment state to the process leaf over that socket. It also streams provider -environment updates after settings polls so future process sessions see +environment updates from the configuration stream so future process sessions see updated provider env without giving the process leaf gateway access. The pre-workload process supervisor is the only accepted control client: the network sidecar verifies its UID, GID, and PID with peer credentials, removes diff --git a/architecture/gateway.md b/architecture/gateway.md index c92c9d9780..e6a010cf3b 100644 --- a/architecture/gateway.md +++ b/architecture/gateway.md @@ -425,8 +425,12 @@ record; sandbox metadata receives the same annotations only as a convenience projection and can retain keys from earlier revisions. Policy revision creation, optional first-policy backfill, metadata projection, and superseding older revisions commit in one database transaction. SQLite serializes this operation -with an immediate transaction, while Postgres locks the sandbox row. A failed -resource-version check or revision insert rolls back the entire operation. +with an immediate transaction. Postgres first locks a dedicated configuration +fence keyed by sandbox ID, then locks the sandbox row. Settings mutations take +the same fence before reading the current policy target. This makes concurrent +policy and settings commits select targets in one database-owned serial order +across gateway replicas. A failed resource-version check, desired-state write, +projection, or operation insert rolls back the entire transaction. SQLite is the default local store; Postgres is supported for deployments that need an external database or multi-replica coordination. Both backends expose @@ -617,12 +621,13 @@ interleave a profile mutation with a sandbox provider-set mutation that would leave an ambiguous final dynamic-token state or a deleted custom profile that is still referenced by a sandbox. -Policy and runtime settings are delivered together through the effective sandbox -config path. A gateway-global policy can override sandbox-scoped policy. The -gateway pushes complete snapshots to active supervisor sessions and periodically -rebuilds them to repair missed delivery. Supervisors hot-reload accepted policy -and acknowledge the exact revision. The legacy poller remains as a mixed-version -compatibility path during this stage. +Policy and runtime settings are delivered together through the supervisor +configuration stream. A gateway-global policy can override sandbox-scoped +policy. The gateway pushes complete snapshots to active supervisor sessions and +owner reconciliation rebuilds them to repair missed delivery. Supervisors +hot-reload accepted policy and acknowledge the exact revision. Gateway and +supervisor protocol revisions must match; there is no configuration polling +compatibility path. External supervisor middleware registration is operator-owned configuration under `[[openshell.supervisor.middleware]]`. At startup the gateway connects to @@ -698,13 +703,36 @@ mutation handlers. Current supervisors establish the stream before gateway-owned runtime initialization, apply bootstrap and live snapshots directly, and persist only -compact component observations from their results. Previous-revision -supervisors retain polling as a rollout fallback, and owner reconciliation +compact component observations from their results. The protocol is +release-matched and no polling compatibility path remains. Owner reconciliation repairs missed or failed delivery from current database state. Snapshot build, fanout, or enqueue failure cannot fail a mutation that already committed. Provider snapshots may contain credentials and must not be persisted or included in logs. +For sandbox-scoped policy and settings mutations, the gateway atomically stores +the desired state and a durable operation whose target is the exact policy and +settings revision tuple. Server-side `WAIT_FOR_APPLY` reads this durable record +until a correlated stream result or authoritative lifecycle transition makes it +terminal. Pending-operation reconciliation provides crash recovery and can run +on a gateway other than the request handler; local notifications are wake-up +hints only. Operations persist revisions, outcome, timestamps, response +metadata, and bounded sanitized errors, never complete configuration payloads +or credentials. The SQL status column changes atomically with the encoded +operation. Result correlation queries only pending operations scoped to the +reporting sandbox. Recovery claims bounded due batches, commits each retry +deadline before snapshot construction, groups work by sandbox, and publishes +each component at most once per sandbox pass. + +Operation records and their idempotency keys currently have no automatic +expiration. The gateway retains both until an explicit deletion contract is +defined, so an idempotency key cannot be reused merely because time passed. +On startup, the gateway decodes every existing operation and repairs its SQL +scope, state, and retry-time projection before selective reconciliation starts. +The repair is restart-safe and does not change operation resource versions. +Gateways that share a database must be upgraded together while this projection +is introduced. An older gateway does not maintain these query columns. + See [sandbox configuration delivery](sandbox.md#supervisor-configuration-delivery) for bootstrap, revision, and supervisor application semantics. diff --git a/architecture/sandbox.md b/architecture/sandbox.md index 2e96520ccf..65fef1a52c 100644 --- a/architecture/sandbox.md +++ b/architecture/sandbox.md @@ -478,19 +478,14 @@ quickly. ## Supervisor Configuration Delivery -The current gateway and supervisor use internal supervisor protocol revision 2. -The gateway accepts Stage 1 revision 1 supervisors through the polling -compatibility path. Peers built before the handshake report revision zero and -remain accepted for one release with a warning and counter because sandboxes -keep their supervisor binary until they are recreated. The gateway includes a +The current gateway and supervisor use internal supervisor protocol revision 3. +This revision is release-matched: both peers reject every other revision. The gateway includes a configuration bootstrap when it accepts a `ConnectSupervisor` session and can send complete component replacements on the same stream after policy, settings, or provider state changes. -Revision 2 supervisors require a complete bootstrap. The gateway uses the same +Supervisors require a complete bootstrap. The gateway uses the same bounded 45-second construction window as other snapshot builds and rejects the -connection when construction fails. Revision 1 compatibility sessions retain -the optional one-second bootstrap budget and use polling when it expires. -The revision 2 supervisor opens the stream and consumes the bootstrap before it +connection when construction fails. The supervisor opens the stream and consumes the bootstrap before it constructs gateway-owned policy, provider state, networking, or the workload. It reports bootstrap results after those components, the workload, and relay endpoints are ready. @@ -533,8 +528,9 @@ revisions suppress unchanged delivery, while failed or timed-out delivery is retried from current database state. Reconnect discards session delivery state and starts with a fresh bootstrap. -Revision 2 does not poll configuration fetch APIs. Polling remains available -only to revision 1 and revision 0 supervisors during the mixed-version rollout. +Supervisors do not poll configuration fetch APIs. Provider credentials arrive +only through the encrypted bootstrap or live stream, and the obsolete +supervisor-only provider-environment fetch RPC no longer exists. The gateway serializes construction per sandbox and component, and coalesces repeated mutations into the latest full snapshot. An enqueue result means only that the local stream queue accepted the message. A bounded scope fanout scheduler @@ -544,8 +540,24 @@ builds. Fanout waits for worker capacity before admitting each recipient, so a fleet-wide change cannot create a fleet-sized task backlog or saturate the store and credential backends. Snapshot construction has a deadline that starts once a build holds a permit, and the gateway rejects encoded stream messages that -approach the transport decoder limit. Durable apply operations and final polling -removal remain separate follow-up work. +approach the transport decoder limit. + +Sandbox-scoped `UpdateConfig` commits the desired policy or settings record and +a non-secret update operation in one database transaction. The operation target +is the exact `(policy_version, settings_revision)` tuple. `COMMIT_ONLY` returns +after that commit; `WAIT_FOR_APPLY` durably waits for `applied`, `inactive`, +`failed`, `superseded`, or `cancelled`. A client timeout does not roll back the +mutation or cancel the operation, and the operation ID is returned in error +metadata for later lookup. Stopped or completed sandboxes finish as `inactive`; +session absence alone never does. A background reconciler reads pending +operations in bounded due batches. It claims retry ownership, groups work by +sandbox, and admits each requested component to the existing bounded delivery +queue at most once for that sandbox. The delivery worker builds the latest +sandbox snapshot once, records its exact revision on every matching pending +operation, then sends that same snapshot. It skips the send if revision +association fails. Local sandbox-state notifications run the same scoped +reconciliation path immediately. The periodic database query remains the +recovery path when another gateway owns the waiter or a notification is missed. ## Policy Revision Acknowledgement @@ -561,8 +573,7 @@ policy structure. Image-specific policy discovery and baseline enrichment can require one initial gateway synchronization. The supervisor commits that repair before runtime initialization, discards the mutation response, and reconnects so it installs -only the fresh authoritative stream bootstrap. Compatibility supervisors retain -the earlier enrichment and first-poll reconciliation path. +only the fresh authoritative stream bootstrap. A newer sandbox-scoped revision can carry the same non-empty effective policy hash as the currently loaded revision, for example when provenance changes @@ -573,23 +584,22 @@ reconciliation succeeds. Global policies, local overrides, equal or older versions, and different hashes do not use this shortcut. Success telemetry is emitted only after the gateway accepts the resulting loaded-status report. -Revision 2 policy status is recorded from the correlated stream result. The -retained reporting RPC uses the same domain helper for compatibility -supervisors. Retryable legacy status delivery uses a FIFO background worker so -status endpoint outages do not block enforcement. +Current policy status is recorded from the correlated stream result. The +retained reporting RPC exists for startup construction failures and uses the +same exact-revision domain helper. Only sandbox-scoped revisions (`PolicySource::Sandbox`, version greater than zero) are acknowledged. Global policies and local-file development policies do not use the sandbox revision API and produce no acknowledgement. When explicit -local Rego and data files are configured, the supervisor continues polling the -gateway for settings and provider refreshes only on the compatibility path; a -revision 2 supervisor receives those components on the stream and never -replaces the local OPA engine with a gateway policy revision. +local Rego and data files are configured, the supervisor still receives +settings and provider refreshes on the stream but never replaces the local OPA +engine with a gateway policy revision. ## Failure Behavior -- If a compatibility configuration poll fails, the sandbox keeps its - last-known-good policy. +- If stream delivery or application fails, owner and operation reconciliation + republish the latest complete desired state while the sandbox retains its + last-known-good policy according to the configured failure mode. - If a live policy or middleware-registry update is invalid, the supervisor rejects the combined update and keeps the current runtime pair. - If an operator-run middleware call fails, the selected config's `on_error` diff --git a/architecture/security-policy.md b/architecture/security-policy.md index 2f1fd2ea1d..69d24bcc27 100644 --- a/architecture/security-policy.md +++ b/architecture/security-policy.md @@ -184,10 +184,10 @@ detection finding at startup naming the inactive controls. The gateway stores sandbox-authored policy revisions separately from derived effective sandbox configuration. Effective configuration can include -gateway-global policy overrides and provider-profile policy layers. The -supervisor polls for config revisions and attempts to load new dynamic policy -into the in-process OPA engine; CLI reads of the latest sandbox policy use the -same effective configuration path. +gateway-global policy overrides and provider-profile policy layers. The gateway +streams complete configuration snapshots to the supervisor, which validates and +loads dynamic policy into the in-process OPA engine. CLI reads of the latest +sandbox policy use the same effective configuration path. The supervisor validates complete effective policy generations before activation. Overlapping endpoint selectors may contribute request allow and diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index a89e3c30b3..1ed91a77da 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -44,7 +44,8 @@ use openshell_bootstrap::{ use openshell_core::net::set_tcp_nodelay_best_effort; use openshell_core::proto::{ ApproveAllDraftChunksRequest, ApproveDraftChunkRequest, BeginRootfsTarStagingRequest, - ClearDraftChunksRequest, CreateSandboxRequest, CreateSandboxTemplateRequest, + ClearDraftChunksRequest, ConfigUpdateConsistency, ConfigUpdateOperation, + ConfigUpdateOperationState, CreateSandboxRequest, CreateSandboxTemplateRequest, CreateSshSessionRequest, DeleteSandboxRequest, DeleteSandboxTemplateRequest, DeleteServiceRequest, ExecSandboxRequest, ExposeServiceRequest, GetCurrentUserRequest, GetDraftHistoryRequest, GetDraftPolicyRequest, GetGatewayConfigRequest, @@ -71,6 +72,48 @@ use tonic::{Code, Status}; const PROVISIONAL_CONTAINER_EXIT_RECONCILIATION_TIMEOUT: Duration = Duration::from_secs(5); +fn report_config_update_operation( + operation: Option<&ConfigUpdateOperation>, + version: u32, +) -> Result<()> { + let operation = + operation.ok_or_else(|| miette!("gateway omitted the requested apply operation"))?; + match ConfigUpdateOperationState::try_from(operation.state).unwrap_or_default() { + ConfigUpdateOperationState::Applied => { + eprintln!( + "{} Policy version {} applied (operation {})", + "✓".green().bold(), + version, + operation.operation_id + ); + Ok(()) + } + ConfigUpdateOperationState::Inactive => { + eprintln!( + "{} Policy version {} committed; sandbox is inactive (operation {})", + "✓".green().bold(), + version, + operation.operation_id + ); + Ok(()) + } + ConfigUpdateOperationState::Failed + | ConfigUpdateOperationState::Superseded + | ConfigUpdateOperationState::Cancelled => Err(miette!( + "policy version {} did not apply: {} (operation {})", + version, + operation.sanitized_error, + operation.operation_id + )), + ConfigUpdateOperationState::Pending | ConfigUpdateOperationState::Unspecified => { + Err(miette!( + "gateway returned a non-terminal apply operation {}", + operation.operation_id + )) + } + } +} + // Re-export SSH functions for backward compatibility pub use crate::ssh::{Editor, print_ssh_config}; pub use crate::ssh::{ @@ -4564,6 +4607,12 @@ pub async fn sandbox_policy_set( name: name.to_string(), policy: Some(policy), workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + consistency: if wait { + ConfigUpdateConsistency::WaitForApply.into() + } else { + ConfigUpdateConsistency::CommitOnly.into() + }, + wait_timeout_secs: u32::try_from(timeout_secs).unwrap_or(u32::MAX), ..Default::default() }) .await @@ -4592,65 +4641,7 @@ pub async fn sandbox_policy_set( return Ok(()); } - // Poll for status until loaded, failed, or timeout. - let deadline = Instant::now() + Duration::from_secs(timeout_secs); - loop { - if Instant::now() > deadline { - eprintln!( - "{} Timeout waiting for policy version {} to load", - "✗".red().bold(), - resp.version - ); - std::process::exit(124); - } - - tokio::time::sleep(Duration::from_secs(1)).await; - - let status_resp = client - .get_sandbox_policy_status(GetSandboxPolicyStatusRequest { - name: name.to_string(), - version: resp.version, - global: false, - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), - }) - .await - .into_diagnostic()?; - - let inner = status_resp.into_inner(); - if let Some(rev) = &inner.revision { - let status = PolicyStatus::try_from(rev.status).unwrap_or(PolicyStatus::Unspecified); - match status { - PolicyStatus::Loaded => { - eprintln!( - "{} Policy version {} loaded (active version: {})", - "✓".green().bold(), - rev.version, - inner.active_version - ); - return Ok(()); - } - PolicyStatus::Failed => { - eprintln!( - "{} Policy version {} failed to load: {}", - "✗".red().bold(), - rev.version, - rev.load_error - ); - std::process::exit(1); - } - PolicyStatus::Superseded => { - eprintln!( - "{} Policy version {} was superseded (active version: {})", - "⚠".yellow().bold(), - rev.version, - inner.active_version - ); - return Ok(()); - } - _ => {} // still pending, keep polling - } - } - } + report_config_update_operation(resp.operation.as_ref(), resp.version) } #[allow(clippy::too_many_arguments)] @@ -4738,6 +4729,12 @@ pub async fn sandbox_policy_update( name: name.to_string(), merge_operations: plan.merge_operations, workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + consistency: if wait { + ConfigUpdateConsistency::WaitForApply.into() + } else { + ConfigUpdateConsistency::CommitOnly.into() + }, + wait_timeout_secs: u32::try_from(timeout_secs).unwrap_or(u32::MAX), ..Default::default() }) .await @@ -4767,64 +4764,7 @@ pub async fn sandbox_policy_update( return Ok(()); } - let deadline = Instant::now() + Duration::from_secs(timeout_secs); - loop { - if Instant::now() > deadline { - eprintln!( - "{} Timeout waiting for policy version {} to load", - "✗".red().bold(), - response.version - ); - std::process::exit(124); - } - - tokio::time::sleep(Duration::from_secs(1)).await; - - let status_resp = client - .get_sandbox_policy_status(GetSandboxPolicyStatusRequest { - name: name.to_string(), - version: response.version, - global: false, - workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), - }) - .await - .into_diagnostic()?; - - let inner = status_resp.into_inner(); - if let Some(rev) = &inner.revision { - let status = PolicyStatus::try_from(rev.status).unwrap_or(PolicyStatus::Unspecified); - match status { - PolicyStatus::Loaded => { - eprintln!( - "{} Policy version {} loaded (active version: {})", - "✓".green().bold(), - rev.version, - inner.active_version - ); - return Ok(()); - } - PolicyStatus::Failed => { - eprintln!( - "{} Policy version {} failed to load: {}", - "✗".red().bold(), - rev.version, - rev.load_error - ); - std::process::exit(1); - } - PolicyStatus::Superseded => { - eprintln!( - "{} Policy version {} was superseded (active version: {})", - "⚠".yellow().bold(), - rev.version, - inner.active_version - ); - return Ok(()); - } - _ => {} - } - } - } + report_config_update_operation(response.operation.as_ref(), response.version) } pub async fn sandbox_policy_get( diff --git a/crates/openshell-cli/tests/ensure_providers_integration.rs b/crates/openshell-cli/tests/ensure_providers_integration.rs index 7b17ee7a35..e6801d5f3e 100644 --- a/crates/openshell-cli/tests/ensure_providers_integration.rs +++ b/crates/openshell-cli/tests/ensure_providers_integration.rs @@ -19,8 +19,7 @@ use openshell_core::proto::{ ExchangeProviderSubjectTokenRequest, ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, GatewayMessage, GetGatewayConfigRequest, GetGatewayConfigResponse, GetProviderRequest, GetSandboxConfigRequest, - GetSandboxConfigResponse, GetSandboxProviderEnvironmentRequest, - GetSandboxProviderEnvironmentResponse, GetSandboxRequest, HealthRequest, HealthResponse, + GetSandboxConfigResponse, GetSandboxRequest, HealthRequest, HealthResponse, ListProvidersRequest, ListProvidersResponse, ListSandboxProvidersRequest, ListSandboxProvidersResponse, ListSandboxesRequest, ListSandboxesResponse, Provider, ProviderResponse, RevokeSshSessionRequest, RevokeSshSessionResponse, SandboxResponse, @@ -203,15 +202,6 @@ impl OpenShell for TestOpenShell { Ok(Response::new(GetGatewayConfigResponse::default())) } - async fn get_sandbox_provider_environment( - &self, - _request: tonic::Request, - ) -> Result, Status> { - Ok(Response::new( - GetSandboxProviderEnvironmentResponse::default(), - )) - } - async fn create_ssh_session( &self, _request: tonic::Request, @@ -530,6 +520,17 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("not implemented in test")) } + #[allow(unused_qualifications)] + async fn get_config_update_operation( + &self, + _request: tonic::Request, + ) -> Result< + tonic::Response, + tonic::Status, + > { + Err(tonic::Status::unimplemented("unused")) + } + async fn update_config( &self, _request: tonic::Request, diff --git a/crates/openshell-cli/tests/mtls_integration.rs b/crates/openshell-cli/tests/mtls_integration.rs index 29de1fe6fe..56c225b2c9 100644 --- a/crates/openshell-cli/tests/mtls_integration.rs +++ b/crates/openshell-cli/tests/mtls_integration.rs @@ -174,16 +174,6 @@ impl OpenShell for TestOpenShell { )) } - async fn get_sandbox_provider_environment( - &self, - _request: tonic::Request, - ) -> Result, Status> - { - Ok(Response::new( - openshell_core::proto::GetSandboxProviderEnvironmentResponse::default(), - )) - } - async fn create_ssh_session( &self, _request: tonic::Request, @@ -387,6 +377,17 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("not implemented in test")) } + #[allow(unused_qualifications)] + async fn get_config_update_operation( + &self, + _request: tonic::Request, + ) -> Result< + tonic::Response, + tonic::Status, + > { + Err(tonic::Status::unimplemented("unused")) + } + async fn update_config( &self, _request: tonic::Request, diff --git a/crates/openshell-cli/tests/provider_commands_integration.rs b/crates/openshell-cli/tests/provider_commands_integration.rs index 7989275667..f88808b9e1 100644 --- a/crates/openshell-cli/tests/provider_commands_integration.rs +++ b/crates/openshell-cli/tests/provider_commands_integration.rs @@ -16,8 +16,7 @@ use openshell_core::proto::{ ExchangeProviderSubjectTokenRequest, ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, GatewayMessage, GetGatewayConfigRequest, GetGatewayConfigResponse, GetProviderRefreshStatusRequest, GetProviderRefreshStatusResponse, - GetProviderRequest, GetSandboxConfigRequest, GetSandboxConfigResponse, - GetSandboxProviderEnvironmentRequest, GetSandboxProviderEnvironmentResponse, GetSandboxRequest, + GetProviderRequest, GetSandboxConfigRequest, GetSandboxConfigResponse, GetSandboxRequest, HealthRequest, HealthResponse, ListProvidersRequest, ListProvidersResponse, ListSandboxProvidersRequest, ListSandboxProvidersResponse, ListSandboxesRequest, ListSandboxesResponse, Provider, ProviderCredentialRefresh, ProviderCredentialRefreshStatus, @@ -342,15 +341,6 @@ impl OpenShell for TestOpenShell { })) } - async fn get_sandbox_provider_environment( - &self, - _request: tonic::Request, - ) -> Result, Status> { - Ok(Response::new( - GetSandboxProviderEnvironmentResponse::default(), - )) - } - async fn create_ssh_session( &self, _request: tonic::Request, @@ -960,6 +950,17 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("not implemented in test")) } + #[allow(unused_qualifications)] + async fn get_config_update_operation( + &self, + _request: tonic::Request, + ) -> Result< + tonic::Response, + tonic::Status, + > { + Err(tonic::Status::unimplemented("unused")) + } + async fn update_config( &self, _request: tonic::Request, diff --git a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs index 5c24fc6695..ef45497e7b 100644 --- a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs +++ b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs @@ -18,14 +18,13 @@ use openshell_core::proto::{ DetachSandboxProviderResponse, ExchangeProviderSubjectTokenRequest, ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, GatewayMessage, GetGatewayConfigRequest, GetGatewayConfigResponse, GetProviderRequest, - GetSandboxConfigRequest, GetSandboxConfigResponse, GetSandboxProviderEnvironmentRequest, - GetSandboxProviderEnvironmentResponse, GetSandboxRequest, GetSandboxTemplateRequest, - GpuResourceRequirements, HealthRequest, HealthResponse, ListProvidersRequest, - ListProvidersResponse, ListSandboxProvidersRequest, ListSandboxProvidersResponse, - ListSandboxTemplatesRequest, ListSandboxTemplatesResponse, ListSandboxesRequest, - ListSandboxesResponse, PlatformEvent, Provider, ProviderResponse, RevokeSshSessionRequest, - RevokeSshSessionResponse, Sandbox, SandboxCondition, SandboxLogLine, SandboxPhase, - SandboxResponse, SandboxStatus, SandboxStreamEvent, SandboxTemplateResponse, + GetSandboxConfigRequest, GetSandboxConfigResponse, GetSandboxRequest, + GetSandboxTemplateRequest, GpuResourceRequirements, HealthRequest, HealthResponse, + ListProvidersRequest, ListProvidersResponse, ListSandboxProvidersRequest, + ListSandboxProvidersResponse, ListSandboxTemplatesRequest, ListSandboxTemplatesResponse, + ListSandboxesRequest, ListSandboxesResponse, PlatformEvent, Provider, ProviderResponse, + RevokeSshSessionRequest, RevokeSshSessionResponse, Sandbox, SandboxCondition, SandboxLogLine, + SandboxPhase, SandboxResponse, SandboxStatus, SandboxStreamEvent, SandboxTemplateResponse, SandboxWorkloadTemplate, ServiceStatus, SettingValue, SupervisorMessage, UpdateProviderRequest, WatchSandboxRequest, sandbox_stream_event, }; @@ -361,15 +360,6 @@ impl OpenShell for TestOpenShell { })) } - async fn get_sandbox_provider_environment( - &self, - _request: tonic::Request, - ) -> Result, Status> { - Ok(Response::new( - GetSandboxProviderEnvironmentResponse::default(), - )) - } - async fn create_ssh_session( &self, request: tonic::Request, @@ -805,6 +795,17 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("not implemented in test")) } + #[allow(unused_qualifications)] + async fn get_config_update_operation( + &self, + _request: tonic::Request, + ) -> Result< + tonic::Response, + tonic::Status, + > { + Err(tonic::Status::unimplemented("unused")) + } + async fn update_config( &self, _request: tonic::Request, diff --git a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs index fdb0ebbd0d..c4ca7df212 100644 --- a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs +++ b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs @@ -17,8 +17,7 @@ use openshell_core::proto::{ ExecSandboxInput, ExecSandboxRequest, GatewayMessage, GetGatewayConfigRequest, GetGatewayConfigResponse, GetProviderRequest, GetSandboxConfigRequest, GetSandboxConfigResponse, GetSandboxPolicyStatusRequest, GetSandboxPolicyStatusResponse, - GetSandboxProviderEnvironmentRequest, GetSandboxProviderEnvironmentResponse, GetSandboxRequest, - HealthRequest, HealthResponse, ListProvidersRequest, ListProvidersResponse, + GetSandboxRequest, HealthRequest, HealthResponse, ListProvidersRequest, ListProvidersResponse, ListSandboxProvidersRequest, ListSandboxProvidersResponse, ListSandboxesRequest, ListSandboxesResponse, NetworkEndpoint, NetworkPolicyRule, PolicyStatus, ProviderResponse, Sandbox, SandboxPolicy, SandboxPolicyRevision, SandboxResponse, SandboxStreamEvent, @@ -236,15 +235,6 @@ impl OpenShell for TestOpenShell { Ok(Response::new(GetGatewayConfigResponse::default())) } - async fn get_sandbox_provider_environment( - &self, - _request: tonic::Request, - ) -> Result, Status> { - Ok(Response::new( - GetSandboxProviderEnvironmentResponse::default(), - )) - } - async fn create_ssh_session( &self, _request: tonic::Request, @@ -438,6 +428,17 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("not implemented in test")) } + #[allow(unused_qualifications)] + async fn get_config_update_operation( + &self, + _request: tonic::Request, + ) -> Result< + tonic::Response, + tonic::Status, + > { + Err(tonic::Status::unimplemented("unused")) + } + async fn update_config( &self, _request: tonic::Request, diff --git a/crates/openshell-core/src/grpc_client.rs b/crates/openshell-core/src/grpc_client.rs index 115b6d6667..73e6e20fcb 100644 --- a/crates/openshell-core/src/grpc_client.rs +++ b/crates/openshell-core/src/grpc_client.rs @@ -24,11 +24,10 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::proto::{ DenialSummary, ExchangeProviderSubjectTokenRequest, GetDraftPolicyRequest, - GetSandboxConfigRequest, GetSandboxProviderEnvironmentRequest, IssueSandboxTokenRequest, - NetworkActivitySummary, PolicyChunk, PolicySource, PolicyStatus, RefreshSandboxTokenRequest, - ReportPolicyStatusRequest, SandboxPolicy as ProtoSandboxPolicy, SubmitPolicyAnalysisRequest, - SubmitPolicyAnalysisResponse, UpdateConfigRequest, open_shell_client::OpenShellClient, - workspace_selector, + GetSandboxConfigRequest, IssueSandboxTokenRequest, NetworkActivitySummary, PolicyChunk, + PolicySource, PolicyStatus, RefreshSandboxTokenRequest, ReportPolicyStatusRequest, + SandboxPolicy as ProtoSandboxPolicy, SubmitPolicyAnalysisRequest, SubmitPolicyAnalysisResponse, + UpdateConfigRequest, open_shell_client::OpenShellClient, workspace_selector, }; use crate::sandbox_env; use miette::{IntoDiagnostic, Result, WrapErr}; @@ -822,38 +821,6 @@ pub async fn sync_policy_and_fetch_snapshot( fetch_settings_snapshot_with_client(&mut client, sandbox_id).await } -/// Fetch provider environment variables for a sandbox from `OpenShell` server via gRPC. -/// -/// Returns a map of environment variable names to values derived from provider -/// credentials configured on the sandbox. Returns an empty map if the sandbox -/// has no providers or the call fails. -pub async fn fetch_provider_environment( - endpoint: &str, - sandbox_id: &str, -) -> Result { - debug!(endpoint = %endpoint, sandbox_id = %sandbox_id, "Fetching provider environment"); - - let mut client = connect(endpoint).await?; - - let response = client - .get_sandbox_provider_environment(GetSandboxProviderEnvironmentRequest { - sandbox_id: sandbox_id.to_string(), - supports_static_credential_bindings: true, - }) - .await - .into_diagnostic()?; - - let inner = response.into_inner(); - Ok(ProviderEnvironmentResult { - environment: inner.environment, - provider_env_revision: inner.provider_env_revision, - credential_expires_at_ms: inner.credential_expires_at_ms, - dynamic_credentials: inner.dynamic_credentials, - static_credential_bindings: inner.static_credential_bindings, - non_secret_environment_keys: inner.non_secret_environment_keys, - }) -} - pub async fn exchange_provider_subject_token( endpoint: &str, sandbox_id: &str, @@ -905,14 +872,14 @@ fn provider_subject_token_exchange_status(status: Status) -> miette::Report { /// A reusable gRPC client for the `OpenShell` service. /// -/// Wraps a tonic channel connected once and reused for policy polling -/// and status reporting, avoiding per-request TLS handshake overhead. +/// Wraps a tonic channel connected once and reused for status reporting and +/// extension-credential rotation, avoiding per-request TLS handshake overhead. #[derive(Clone)] pub struct CachedOpenShellClient { client: OpenShellClient, workspace: Arc>, /// Extension credentials for this supervisor. Cloning the client shares - /// the store, so the middleware registry and the polling loop that rotates + /// the store, so the middleware registry and the stream loop that rotates /// it observe the same slots. extension_credentials: ExtensionCredentialStore, } @@ -924,6 +891,7 @@ pub struct SettingsPollResult { pub version: u32, pub policy_hash: String, pub config_revision: u64, + pub settings_revision: u64, pub policy_source: PolicySource, /// Effective settings keyed by name. pub settings: HashMap, @@ -945,6 +913,7 @@ fn settings_poll_result(inner: crate::proto::GetSandboxConfigResponse) -> Settin version: inner.version, policy_hash: inner.policy_hash, config_revision: inner.config_revision, + settings_revision: inner.settings_revision, policy_source: PolicySource::try_from(inner.policy_source) .unwrap_or(PolicySource::Unspecified), settings: inner.settings, @@ -967,6 +936,7 @@ impl From for SettingsPollResult { version: inner.version, policy_hash: inner.policy_hash, config_revision: inner.config_revision, + settings_revision: inner.settings_revision, policy_source: PolicySource::try_from(inner.policy_source) .unwrap_or(PolicySource::Unspecified), settings: inner.settings, @@ -1149,7 +1119,7 @@ impl CachedOpenShellClient { endpoint: &str, extension_credentials: ExtensionCredentialStore, ) -> Result { - debug!(endpoint = %endpoint, "Connecting openshell gRPC client for policy polling"); + debug!(endpoint = %endpoint, "Connecting reusable openshell gRPC client"); let client = connect(endpoint).await?; Ok(Self { client, @@ -1222,7 +1192,7 @@ impl CachedOpenShellClient { } /// Rotate every credential currently retained by the installed registry. - /// This remains available when configuration polling fails independently. + /// This remains available independently of streamed configuration updates. pub async fn refresh_installed_extension_credentials(&self) -> Result<()> { let names = self.extension_credentials.names(); if names.is_empty() || !self.extension_credentials.needs_refresh(&names, now_ms()) { diff --git a/crates/openshell-core/src/proposals.rs b/crates/openshell-core/src/proposals.rs index 53586629ca..d0868b1651 100644 --- a/crates/openshell-core/src/proposals.rs +++ b/crates/openshell-core/src/proposals.rs @@ -4,7 +4,7 @@ //! Shared state controlling agent-driven policy proposals. //! //! Initialised once during sandbox start from the `agent_policy_proposals_enabled` -//! setting and updated by the policy poll loop or authoritative sidecar control +//! setting and updated by the stream configuration loop or authoritative sidecar control //! when the setting changes. Read by the `policy.local` route handler and by //! the skills installer to gate the agent-controlled mutation surface. diff --git a/crates/openshell-core/src/proto/mod.rs b/crates/openshell-core/src/proto/mod.rs index 1a286be762..80894143a2 100644 --- a/crates/openshell-core/src/proto/mod.rs +++ b/crates/openshell-core/src/proto/mod.rs @@ -100,19 +100,7 @@ pub fn all_workspaces_selector() -> WorkspaceSelector { /// The supervisor stream is an internal, version-locked deployment contract. /// Bump this when either peer can no longer honor the previous stream /// semantics. -pub const SUPERVISOR_PROTOCOL_REVISION: u32 = 2; - -/// Stage 1 peers understand snapshot envelopes but do not apply them. They -/// remain compatible while polling is retained for the rollout. -pub const PREVIOUS_SUPERVISOR_PROTOCOL_REVISION: u32 = 1; - -/// Revision implied by peers built before the handshake existed. Proto3 leaves -/// the field unset, so such peers report zero. -/// -/// Sandboxes keep their supervisor binary until they are recreated, so a -/// gateway upgrade must keep serving them for one release. Remove this -/// allowance once every supported release sends an explicit revision. -pub const LEGACY_SUPERVISOR_PROTOCOL_REVISION: u32 = 0; +pub const SUPERVISOR_PROTOCOL_REVISION: u32 = 3; #[cfg(test)] mod tests { diff --git a/crates/openshell-driver-kubernetes/README.md b/crates/openshell-driver-kubernetes/README.md index b64bf0c6e4..7021094a5e 100644 --- a/crates/openshell-driver-kubernetes/README.md +++ b/crates/openshell-driver-kubernetes/README.md @@ -47,7 +47,7 @@ not a tenant isolation boundary. The gateway stores platform state and delegates sandbox workload creation to this driver. Kubernetes owns scheduling and pod lifecycle. The `openshell-sandbox` supervisor inside each workload owns agent isolation, -credential injection, policy polling, logs, and the gateway relay. +credential injection, stream-delivered policy updates, logs, and the gateway relay. ## Sandbox Resource diff --git a/crates/openshell-sandbox/src/lib.rs b/crates/openshell-sandbox/src/lib.rs index 5ac5fa4dd2..bc3a2541c7 100644 --- a/crates/openshell-sandbox/src/lib.rs +++ b/crates/openshell-sandbox/src/lib.rs @@ -38,8 +38,8 @@ use openshell_core::PolicyValidationFailureMode; use openshell_ocsf::{ ActionId, ActivityId, AppLifecycleBuilder, ConfidenceId, ConfigStateChangeBuilder, - DetectionFindingBuilder, DispositionId, EventContext, FindingInfo, OcsfEvent, SeverityId, - StateId, StatusId, ocsf_emit, + DetectionFindingBuilder, DispositionId, EventContext, FindingInfo, SeverityId, StateId, + StatusId, ocsf_emit, }; // --------------------------------------------------------------------------- @@ -312,7 +312,7 @@ pub async fn run_sandbox( // Extension credentials are owned by this supervisor and shared by every // gateway connection it opens, so the middleware registry's bearer slots - // and the policy poll loop that rotates them stay the same objects. + // and the stream configuration loop that rotates them stay the same objects. let extension_credentials = openshell_extension_core::ExtensionCredentialStore::new(); // Load policy and initialize OPA engine @@ -325,7 +325,7 @@ pub async fn run_sandbox( middleware_registry_status, loaded_policy_origin, mut initial_agent_proposals_enabled, - mut initial_extension_authentication_enabled, + mut _initial_extension_authentication_enabled, ) = if let Some(bootstrap) = sidecar_bootstrap.as_ref() { let (policy, opa_engine, retained_proto, loaded_policy_origin) = load_policy_from_sidecar_bootstrap(bootstrap)?; @@ -341,7 +341,6 @@ pub async fn run_sandbox( } else { load_policy( sandbox_id.clone(), - sandbox, openshell_endpoint.clone(), policy_rules, policy_data, @@ -357,7 +356,7 @@ pub async fn run_sandbox( .and_then(|bootstrap| bootstrap.sandbox_config.as_ref()) { initial_agent_proposals_enabled = agent_proposals_enabled_from_settings(&snapshot.settings); - initial_extension_authentication_enabled = snapshot.extension_authentication_enabled; + _initial_extension_authentication_enabled = snapshot.extension_authentication_enabled; } // Normalize the active driver's identity contract once, while both the @@ -390,6 +389,7 @@ pub async fn run_sandbox( ); #[cfg_attr(not(target_os = "linux"), allow(unused_mut))] + #[allow(clippy::option_if_let_else)] let (provider_credentials, mut provider_env, provider_bootstrap_degraded) = if let Some( bootstrap, ) = @@ -447,102 +447,13 @@ pub async fn run_sandbox( true, ) } else { - // Fetch provider environment variables from the server. - // This is done after loading the policy so the sandbox can still start - // even if provider env fetch fails (graceful degradation). - let ( - provider_env_revision, - provider_env, - provider_credential_expires_at_ms, - dynamic_credentials, - static_credential_bindings, - non_secret_environment_keys, - ) = if let (Some(id), Some(endpoint)) = (&sandbox_id, &openshell_endpoint) { - match openshell_core::grpc_client::fetch_provider_environment(endpoint, id).await { - Ok(result) => { - ocsf_emit!( - ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::Informational) - .status(StatusId::Success) - .state(StateId::Enabled, "loaded") - .message(format!( - "Fetched provider environment [env_count:{}]", - result.environment.len() - )) - .build() - ); - ( - result.provider_env_revision, - result.environment, - result.credential_expires_at_ms, - result.dynamic_credentials, - result.static_credential_bindings, - result.non_secret_environment_keys, - ) - } - Err(e) => { - ocsf_emit!( - ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::High) - .status(StatusId::Failure) - .state(StateId::Disabled, "fail_closed") - .message(format!( - "Failed to fetch provider environment; no provider credentials are active: {e}" - )) - .build() - ); - ( - 0, - std::collections::HashMap::new(), - std::collections::HashMap::new(), - std::collections::HashMap::new(), - std::collections::HashMap::new(), - Vec::new(), - ) - } - } - } else { - ( - 0, - std::collections::HashMap::new(), - std::collections::HashMap::new(), - std::collections::HashMap::new(), - std::collections::HashMap::new(), - Vec::new(), - ) - }; - - let dynamic_credentials_fallback = dynamic_credentials.clone(); - let provider_credentials = match ProviderCredentialState::from_bound_environment( - provider_env_revision, - provider_env, - provider_credential_expires_at_ms, - dynamic_credentials, - static_credential_bindings, - non_secret_environment_keys, - ) { - Ok(credentials) => credentials, - Err(error) => { - ocsf_emit!( - ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::High) - .status(StatusId::Failure) - .state(StateId::Disabled, "fail_closed") - .message(format!( - "Rejected provider environment bindings; static provider credentials were revoked; fetched dynamic token grants remain active: {error}" - )) - .build() - ); - ProviderCredentialState::from_environment( - provider_env_revision, - std::collections::HashMap::new(), - std::collections::HashMap::new(), - dynamic_credentials_fallback, - ) - } - }; - let provider_env = provider_credentials.child_env_with_gcp_resolved(); - (provider_credentials, provider_env, false) + // Offline/file mode has no gateway-owned provider state. Online mode + // receives the complete environment in the required stream bootstrap. + ( + ProviderCredentialState::from_child_env_snapshot(0, std::collections::HashMap::new()), + std::collections::HashMap::new(), + false, + ) }; let mut prepared_bootstrap_result = stream_bootstrap.as_ref().map(|bootstrap| { @@ -598,7 +509,7 @@ pub async fn run_sandbox( // Shared agent-proposals feature flag. Seed from the same initial settings // snapshot that produced the policy so networking and process setup agree - // before the poll loop starts reconciling later changes. + // before the configuration stream starts reconciling later changes. let agent_proposals = AgentProposals::new(initial_agent_proposals_enabled); let process_control_writer = process_control_connection @@ -751,8 +662,8 @@ pub async fn run_sandbox( #[cfg(not(target_os = "linux"))] drop(bypass_activity_tx); - // Workspace watch: the policy poll loop learns the workspace from - // GetSandboxConfig and broadcasts it. Flush tasks and the policy.local + // Workspace watch: the stream bootstrap supplies the workspace and the + // configuration loop broadcasts it. Flush tasks and the policy.local // API read the current value so proposals target the correct workspace. let (workspace_tx, workspace_rx) = tokio::sync::watch::channel(String::new()); let (config_apply_tx, config_apply_rx) = tokio::sync::mpsc::channel(16); @@ -966,43 +877,34 @@ pub async fn run_sandbox( }); } - // Spawn background policy poll task (gRPC mode only). + // Spawn the stream configuration apply task (gRPC mode only). if !process_uses_sidecar_control - && let (Some(id), Some(endpoint), Some(engine)) = ( - sandbox_id.as_deref(), - openshell_endpoint.as_deref(), - opa_engine.as_ref(), - ) + && sandbox_id.is_some() + && let (Some(endpoint), Some(engine)) = (openshell_endpoint.as_deref(), opa_engine.as_ref()) { - let poll_id = id.to_string(); - let poll_endpoint = endpoint.to_string(); - let poll_engine = engine.clone(); - let poll_ocsf_enabled = ocsf_enabled.clone(); - let poll_ocsf_schema_version = ocsf_schema_version.clone(); - let poll_pid = entrypoint_pid.clone(); - let poll_provider_credentials = provider_credentials.clone(); - let poll_policy_local = networking.as_ref().map(|n| n.policy_local_ctx.clone()); - let poll_interval_secs: u64 = std::env::var("OPENSHELL_POLICY_POLL_INTERVAL_SECS") - .ok() - .and_then(|v| v.parse().ok()) - .unwrap_or(10); - let poll_ctx = PolicyPollLoopContext { - endpoint: poll_endpoint, - sandbox_id: poll_id, - opa_engine: poll_engine, + let stream_endpoint = endpoint.to_string(); + let stream_engine = engine.clone(); + let stream_ocsf_enabled = ocsf_enabled.clone(); + let stream_ocsf_schema_version = ocsf_schema_version.clone(); + let stream_pid = entrypoint_pid.clone(); + let stream_provider_credentials = provider_credentials.clone(); + let stream_policy_local = networking.as_ref().map(|n| n.policy_local_ctx.clone()); + let credential_refresh_interval_secs = 10; + let stream_config_ctx = StreamConfigLoopContext { + endpoint: stream_endpoint, + opa_engine: stream_engine, loaded_policy_origin, - entrypoint_pid: poll_pid, - interval_secs: poll_interval_secs, - ocsf_enabled: poll_ocsf_enabled, - ocsf_schema_version: poll_ocsf_schema_version, - provider_credentials: poll_provider_credentials, - policy_local_ctx: poll_policy_local, + entrypoint_pid: stream_pid, + interval_secs: credential_refresh_interval_secs, + ocsf_enabled: stream_ocsf_enabled, + ocsf_schema_version: stream_ocsf_schema_version, + provider_credentials: stream_provider_credentials, + policy_local_ctx: stream_policy_local, agent_proposals: agent_proposals.clone(), middleware_registry_status, sidecar_control_publisher: sidecar_control_publisher.clone(), workspace_tx, extension_credentials: extension_credentials.clone(), - extension_authentication_enabled: initial_extension_authentication_enabled, middleware_connector: default_middleware_connector(), transparent_tcp: TransparentTcpReloadState { capable: transparent_tcp_capable, @@ -1016,13 +918,15 @@ pub async fn run_sandbox( }; tokio::spawn(async move { - if let Err(e) = run_policy_poll_loop(poll_ctx).await { + if let Err(e) = run_stream_config_loop(stream_config_ctx).await { ocsf_emit!( AppLifecycleBuilder::new(ocsf_ctx()) .activity(ActivityId::Fail) .severity(SeverityId::Medium) .status(StatusId::Failure) - .message(format!("Policy poll loop exited with error: {e}")) + .message(format!( + "Stream configuration apply loop exited with error: {e}" + )) .build() ); } @@ -2569,7 +2473,6 @@ where /// resolution after the container entrypoint starts. async fn load_policy( sandbox_id: Option, - sandbox: Option, openshell_endpoint: Option, policy_rules: Option, policy_data: Option, @@ -2679,129 +2582,28 @@ async fn load_policy( )); } - // Gateway mode: consume a stream snapshot for the current protocol or - // fetch one for compatibility supervisors, then construct the OPA engine. + // Gateway mode: consume the required stream bootstrap, then construct the + // OPA engine. Configuration fetch polling is not a protocol fallback. if let (Some(id), Some(endpoint)) = (&sandbox_id, &openshell_endpoint) { - let stream_bootstrap = initial_snapshot.is_some(); - if stream_bootstrap { - info!(sandbox_id = %id, "Loading sandbox policy from supervisor bootstrap"); - } else { - info!( - sandbox_id = %id, - endpoint = %endpoint, - "Fetching sandbox policy via compatibility RPC" - ); - } - let mut snapshot = if let Some(snapshot) = initial_snapshot { - snapshot.into() - } else { - grpc_retry("Policy fetch", || { - openshell_core::grpc_client::fetch_settings_snapshot(endpoint, id) - }) - .await? - }; - - let mut proto_policy = if let Some(p) = snapshot.policy.clone() { - p - } else if stream_bootstrap { - return Err(miette::miette!( - "supervisor bootstrap omitted required sandbox policy" - )); - } else { - // No policy configured on the server. Discover from disk or - // fall back to the restrictive default, then sync to the - // gateway so it becomes the authoritative baseline. - ocsf_emit!( - ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::Informational) - .status(StatusId::Success) - .state(StateId::Other, "discovery") - .message("Server returned no policy; attempting local discovery") - .build() - ); - let mut discovered = discover_policy_from_disk_or_default(); - // Enrich before syncing so the gateway baseline includes - // baseline paths from the start. - enrich_proto_baseline_paths(&mut discovered); - strip_proto_provider_policy_entries(&mut discovered); - let sandbox = sandbox.as_deref().ok_or_else(|| { - miette::miette!( - "Cannot sync discovered policy: sandbox not available.\n\ - Set OPENSHELL_SANDBOX or --sandbox to enable policy sync." - ) - })?; - - // Sync and re-fetch over a single connection to avoid extra - // TLS handshakes. - let ws = snapshot.workspace.clone(); - snapshot = grpc_retry("Policy discovery sync", || { - openshell_core::grpc_client::sync_policy_and_fetch_snapshot( - endpoint, - id, - sandbox, - &discovered, - &ws, - ) - }) - .await?; - snapshot.policy.clone().ok_or_else(|| { - miette::miette!("Server still returned no policy after sync — this is a bug") - })? - }; - - // True only while `snapshot` describes the exact policy that will be - // constructed below. If enrichment cannot be synced and re-fetched, - // the policy remains enforceable but cannot be acknowledged by - // inferred structural equality. - let mut policy_bound_to_snapshot = true; + info!(sandbox_id = %id, "Loading sandbox policy from supervisor bootstrap"); + let snapshot: openshell_core::grpc_client::SettingsPollResult = initial_snapshot + .ok_or_else(|| miette::miette!("supervisor stream bootstrap is required"))? + .into(); + let mut proto_policy = snapshot.policy.clone().ok_or_else(|| { + miette::miette!("supervisor bootstrap omitted required sandbox policy") + })?; // Ensure baseline filesystem paths are present for proxy-mode // sandboxes. If the policy was enriched, sync the updated version // back to the gateway so users can see the effective policy. let enriched = enrich_proto_baseline_paths(&mut proto_policy); let sync_policy = proto_sync_payload_for_enriched_policy(&proto_policy, enriched); - if stream_bootstrap && sync_policy.is_some() { + if sync_policy.is_some() { return Err(miette::miette!( "supervisor bootstrap policy omitted required baseline paths" )); } - if let Some(sync_policy) = sync_policy { - if let Some(sandbox_name) = sandbox.as_deref() { - match openshell_core::grpc_client::sync_policy_and_fetch_snapshot( - endpoint, - id, - sandbox_name, - &sync_policy, - &snapshot.workspace, - ) - .await - { - Ok(canonical) => { - if let Some(policy) = canonical.policy.clone() { - proto_policy = policy; - snapshot = canonical; - } else { - policy_bound_to_snapshot = false; - warn!( - "Gateway returned no policy after enrichment sync; initial revision will be reconciled" - ); - } - } - Err(e) => { - policy_bound_to_snapshot = false; - warn!( - error = %e, - "Failed to sync enriched policy back to gateway; initial revision will be reconciled" - ); - } - } - } else { - policy_bound_to_snapshot = false; - } - } - - let mut loaded_policy_revision = - policy_bound_to_snapshot.then(|| LoadedPolicyRevision::from_snapshot(&snapshot)); + let loaded_policy_revision = LoadedPolicyRevision::from_snapshot(&snapshot); // Build OPA engine from baked-in rules + typed proto data. // In cluster mode, proxy networking is always enabled so OPA is @@ -2810,44 +2612,11 @@ async fn load_policy( // container hasn't started yet. After the entrypoint spawns, the // engine is rebuilt with the real PID for symlink resolution. info!("Creating OPA engine from proto policy data"); - let mut has_last_valid_policy = true; - let engine = match OpaEngine::from_proto(&proto_policy) { - Ok(engine) => Arc::new(engine), - Err(e) => { - if stream_bootstrap { - return Err(e).wrap_err( - "failed to install required sandbox policy from supervisor bootstrap", - ); - } - report_initial_policy_failure(endpoint, id, loaded_policy_revision.as_ref(), &e) - .await; - let validation_error = e.to_string(); - let candidate_version = snapshot.version; - let candidate_hash = snapshot.policy_hash.clone(); - // There is no in-memory last-known-good generation during - // startup, so both configured modes necessarily fail closed. - // Load the restrictive default atomically and keep the - // rejected revision unacknowledged for poll reconciliation. - has_last_valid_policy = false; - proto_policy = openshell_policy::restrictive_default_policy(); - let engine = Arc::new(OpaEngine::from_proto(&proto_policy)?); - let disposition = apply_policy_validation_failure( - &engine, - snapshot.policy_validation_failure_mode, - has_last_valid_policy, - candidate_version, - &validation_error, - )?; - emit_policy_validation_failure( - &disposition, - candidate_version, - &candidate_hash, - &validation_error, - ); - loaded_policy_revision = None; - engine - } - }; + let has_last_valid_policy = true; + let engine = Arc::new( + OpaEngine::from_proto(&proto_policy) + .wrap_err("failed to install required sandbox policy from supervisor bootstrap")?, + ); // Install the in-process catalog before any external connection can // fail. A newly started sandbox must always be able to resolve built-in @@ -2856,7 +2625,7 @@ async fn load_policy( // Connect operator-registered middleware services. A connect/describe // failure keeps the built-in registry active so each request's - // `on_error` policy governs matched traffic. The policy poll loop + // `on_error` policy governs matched traffic. The stream configuration loop // retries the install without waiting for a config change. let middleware_services = snapshot.supervisor_middleware_services.clone(); let middleware_registry_status = if middleware_services.is_empty() { @@ -2868,7 +2637,7 @@ async fn load_policy( async move { let credentials = if extension_authentication_enabled { // Share the supervisor's store so the slots installed here - // are the ones the policy poll loop later rotates in place. + // are the ones the stream configuration loop later rotates in place. openshell_core::grpc_client::CachedOpenShellClient::connect_with_credentials( endpoint, extension_credentials, @@ -2892,26 +2661,9 @@ async fn load_policy( .await .and_then(|registry| engine.replace_middleware_registry(registry)) { - if stream_bootstrap { - return Err(error).wrap_err( - "failed to install required middleware runtime from supervisor bootstrap", - ); - } - ocsf_emit!( - ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::Medium) - .status(StatusId::Failure) - .state(StateId::Other, "degraded") - .unmapped( - "supervisor_middleware_service_count", - serde_json::json!(middleware_services.len()) - ) - .message(format!( - "Supervisor middleware connect failed at startup; continuing with built-in middleware only, per-request on_error governs matched requests [error:{error}]" - )) - .build() + return Err(error).wrap_err( + "failed to install required middleware runtime from supervisor bootstrap", ); - MiddlewareRegistryStatus::NeedsReconciliation } else { MiddlewareRegistryStatus::Synchronized }; @@ -2920,7 +2672,7 @@ async fn load_policy( let policy = match SandboxPolicy::try_from(proto_policy.clone()) { Ok(policy) => policy, Err(e) => { - report_initial_policy_failure(endpoint, id, loaded_policy_revision.as_ref(), &e) + report_initial_policy_failure(endpoint, id, Some(&loaded_policy_revision), &e) .await; return Err(e); } @@ -2931,7 +2683,7 @@ async fn load_policy( Some(proto_policy), middleware_registry_status, LoadedPolicyOrigin::Gateway { - revision: loaded_policy_revision, + revision: Some(loaded_policy_revision), has_last_valid_policy, }, agent_proposals_enabled_from_settings(&snapshot.settings), @@ -3068,6 +2820,7 @@ enum GatewayRuntimeReloadError { MiddlewareRegistry(miette::Report), } +#[cfg(test)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum GatewayRuntimeFailureClass { PolicyValidation, @@ -3076,6 +2829,7 @@ enum GatewayRuntimeFailureClass { } impl GatewayRuntimeReloadError { + #[cfg(test)] fn class(&self) -> GatewayRuntimeFailureClass { match self { Self::PolicyValidation(_) => GatewayRuntimeFailureClass::PolicyValidation, @@ -3087,6 +2841,7 @@ impl GatewayRuntimeReloadError { } } +#[cfg(test)] #[derive(Debug, PartialEq, Eq)] struct FailedRuntimeRevision { config_revision: u64, @@ -3094,6 +2849,7 @@ struct FailedRuntimeRevision { failure_class: GatewayRuntimeFailureClass, } +#[cfg(test)] impl FailedRuntimeRevision { fn new(config_revision: u64, policy_hash: &str, failure: &GatewayRuntimeReloadError) -> Self { Self { @@ -3190,6 +2946,7 @@ fn middleware_registry_needs_rebuild( || current_services != desired_services } +#[cfg(test)] fn gateway_policy_runtime_needs_reconciliation( reloads_gateway_policy: bool, current_policy_hash: &str, @@ -3220,7 +2977,8 @@ struct LoadedPolicyRevision { /// /// A missing gateway revision means the policy was loaded from the gateway but /// could not be bound to an authoritative snapshot (for example, enrichment -/// sync failed). That state must reconcile on the first successful poll. A +/// sync failed). That state must reconcile on the first successful streamed +/// snapshot. A /// local-file override is different: gateway policy revisions are observed for /// settings/provider refreshes but must never replace the explicit local OPA /// policy. @@ -3260,149 +3018,6 @@ impl LoadedPolicyRevision { } } -/// A sandbox-scoped policy revision that was constructed successfully at -/// startup and must be acknowledged to the gateway exactly once. -#[derive(Clone, Debug, PartialEq, Eq)] -struct InitialPolicyAck { - version: u32, - policy_hash: String, - config_revision: u64, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -struct PolicyStatusUpdate { - version: u32, - loaded: bool, - error: String, - success_event: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -enum PolicyStatusSuccessEvent { - InitialAcknowledgement { policy_hash: String }, - UnchangedAcknowledgement { policy_hash: String }, -} - -impl PolicyStatusUpdate { - fn initial_loaded(ack: &InitialPolicyAck) -> Self { - Self { - version: ack.version, - loaded: true, - error: String::new(), - success_event: Some(PolicyStatusSuccessEvent::InitialAcknowledgement { - policy_hash: ack.policy_hash.clone(), - }), - } - } - - fn loaded(version: u32) -> Self { - Self { - version, - loaded: true, - error: String::new(), - success_event: None, - } - } - - fn unchanged_loaded(version: u32, policy_hash: String) -> Self { - Self { - version, - loaded: true, - error: String::new(), - success_event: Some(PolicyStatusSuccessEvent::UnchangedAcknowledgement { policy_hash }), - } - } - - fn failed(version: u32, error: String) -> Self { - Self { - version, - loaded: false, - error, - success_event: None, - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -enum InitialPollDisposition { - Acknowledge(InitialPolicyAck), - Reconcile, - TrackOnly, -} - -/// Determine whether the initially loaded policy corresponds to an -/// authoritative sandbox-scoped revision that must be acknowledged. -/// -/// Returns `Some` only for sandbox-sourced revisions (version > 0) whose -/// captured gateway identity matches the current version and hash. Global -/// policies, local-file development policies, version zero, and changed -/// identities yield `None`, so those paths never emit a sandbox-revision -/// acknowledgement. -fn initial_policy_ack_candidate( - loaded: Option<&LoadedPolicyRevision>, - canonical: &openshell_core::grpc_client::SettingsPollResult, -) -> Option { - let loaded = loaded?; - if loaded.policy_source != openshell_core::proto::PolicySource::Sandbox - || canonical.policy_source != openshell_core::proto::PolicySource::Sandbox - { - return None; - } - if loaded.version == 0 || canonical.version == 0 { - return None; - } - if loaded.version != canonical.version - || loaded.policy_hash != canonical.policy_hash - || canonical.config_revision < loaded.config_revision - { - return None; - } - Some(InitialPolicyAck { - version: loaded.version, - policy_hash: loaded.policy_hash.clone(), - config_revision: canonical.config_revision, - }) -} - -fn initial_poll_disposition( - origin: &LoadedPolicyOrigin, - canonical: &openshell_core::grpc_client::SettingsPollResult, -) -> InitialPollDisposition { - match origin { - LoadedPolicyOrigin::LocalOverride => InitialPollDisposition::TrackOnly, - LoadedPolicyOrigin::Gateway { revision, .. } => { - initial_policy_ack_candidate(revision.as_ref(), canonical).map_or( - InitialPollDisposition::Reconcile, - InitialPollDisposition::Acknowledge, - ) - } - } -} - -fn unchanged_policy_revision_candidate( - reloads_gateway_policy: bool, - recovering_rejected_policy: bool, - current_policy_version: u32, - current_policy_hash: &str, - result: &openshell_core::grpc_client::SettingsPollResult, -) -> Option { - (reloads_gateway_policy - && !recovering_rejected_policy - && !current_policy_hash.is_empty() - && result.policy_source == openshell_core::proto::PolicySource::Sandbox - && result.version > current_policy_version - && result.policy_hash == current_policy_hash) - .then_some(result.version) -} - -fn unchanged_policy_revision_ready_to_ack( - candidate: Option, - policy_runtime_changed: bool, - policy_runtime_reconciled: bool, -) -> Option { - candidate.filter(|_| !policy_runtime_changed || policy_runtime_reconciled) -} - /// Whether the credential-provenance gates cannot apply to the loaded policy. /// /// The gateway derives `provider_credentialed` and deliberately keeps it out of @@ -3460,26 +3075,8 @@ fn report_credential_gating_unavailable() { ); } -/// Deliver policy status updates independently from policy reconciliation. -/// -/// The channel is FIFO, so a delayed older status can never arrive after a -/// newer status and move the gateway's active version backward. Delivery uses -/// the existing bounded retry, but failures never delay policy enforcement. #[tonic::async_trait] trait PolicyGatewayClient: Clone + Send + Sync + 'static { - async fn poll_settings( - &self, - sandbox_id: &str, - ) -> Result; - - async fn report_policy_status( - &self, - sandbox_id: &str, - version: u32, - loaded: bool, - error: &str, - ) -> Result<()>; - async fn refresh_installed_extension_credentials(&self) -> Result<()> { Ok(()) } @@ -3490,30 +3087,10 @@ trait PolicyGatewayClient: Clone + Send + Sync + 'static { ) -> Result> { Ok(std::collections::HashMap::new()) } - - fn workspace(&self) -> String; } #[tonic::async_trait] impl PolicyGatewayClient for openshell_core::grpc_client::CachedOpenShellClient { - async fn poll_settings( - &self, - sandbox_id: &str, - ) -> Result { - self.poll_settings(sandbox_id).await - } - - async fn report_policy_status( - &self, - sandbox_id: &str, - version: u32, - loaded: bool, - error: &str, - ) -> Result<()> { - self.report_policy_status(sandbox_id, version, loaded, error) - .await - } - async fn refresh_installed_extension_credentials(&self) -> Result<()> { self.refresh_installed_extension_credentials().await } @@ -3524,101 +3101,6 @@ impl PolicyGatewayClient for openshell_core::grpc_client::CachedOpenShellClient ) -> Result> { self.extension_credentials_for(services).await } - - fn workspace(&self) -> String { - self.workspace() - } -} - -async fn run_policy_status_reporter( - client: C, - sandbox_id: String, - mut updates: tokio::sync::mpsc::UnboundedReceiver, -) { - 'updates: while let Some(update) = updates.recv().await { - let operation = if matches!( - update.success_event, - Some(PolicyStatusSuccessEvent::InitialAcknowledgement { .. }) - ) { - "Initial policy acknowledgement" - } else { - "Policy status report" - }; - let mut attempt = 1_u32; - loop { - let sandbox_id = sandbox_id.clone(); - let error = update.error.clone(); - let client = client.clone(); - match client - .report_policy_status(&sandbox_id, update.version, update.loaded, &error) - .await - { - Ok(()) => break, - Err(error) if is_retryable_error(&error) => { - let backoff = Duration::from_secs(1_u64 << attempt.saturating_sub(1).min(5)); - warn!( - %error, - attempt, - version = update.version, - loaded = update.loaded, - retry_in_secs = backoff.as_secs(), - "{operation} failed transiently; retaining ordered update" - ); - tokio::time::sleep(backoff).await; - attempt = attempt.saturating_add(1); - } - Err(error) => { - warn!( - %error, - version = update.version, - loaded = update.loaded, - "Discarding terminal policy status update" - ); - continue 'updates; - } - } - } - - if let Some(event) = update.success_event { - let (policy_hash, message) = match event { - PolicyStatusSuccessEvent::InitialAcknowledgement { policy_hash } => ( - policy_hash, - format!( - "Acknowledged initial policy revision as loaded [version:{}]", - update.version - ), - ), - PolicyStatusSuccessEvent::UnchangedAcknowledgement { policy_hash } => ( - policy_hash, - format!( - "Acknowledged unchanged policy revision as loaded [version:{}]", - update.version - ), - ), - }; - ocsf_emit!( - ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::Informational) - .status(StatusId::Success) - .state(StateId::Enabled, "loaded") - .unmapped("version", serde_json::json!(update.version)) - .unmapped("policy_hash", serde_json::json!(policy_hash)) - .message(message) - .build() - ); - } - } -} - -fn enqueue_policy_status(sender: &UnboundedSender, update: PolicyStatusUpdate) { - let version = update.version; - if let Err(error) = sender.send(update) { - warn!( - %error, - version, - "Policy status reporter unavailable during shutdown" - ); - } } /// Best-effort `FAILED` acknowledgement when initial policy construction or @@ -3662,21 +3144,14 @@ async fn report_initial_policy_failure( } } -/// Background loop that polls the server for policy updates. -/// -/// When a new version is detected, attempts to reload the OPA engine via -/// `reload_from_proto_with_pid()`. Reports load success/failure back to the -/// server. On failure, the previous engine is untouched (LKG behavior). -/// -/// When the entrypoint PID is available, policy reloads include symlink -/// resolution for binary paths via the container filesystem. -struct PolicyPollLoopContext { +/// Background loop that applies complete desired-state snapshots delivered by +/// the authenticated supervisor stream. +struct StreamConfigLoopContext { endpoint: String, - sandbox_id: String, opa_engine: Arc, /// Source of the policy currently loaded into OPA. This distinguishes an /// explicit local-file override from an unbound gateway revision so the - /// former is never replaced by policy polling. + /// former is never replaced by a gateway-delivered policy snapshot. loaded_policy_origin: LoadedPolicyOrigin, entrypoint_pid: Arc, interval_secs: u64, @@ -3689,7 +3164,6 @@ struct PolicyPollLoopContext { sidecar_control_publisher: Option, workspace_tx: tokio::sync::watch::Sender, extension_credentials: openshell_extension_core::ExtensionCredentialStore, - extension_authentication_enabled: bool, middleware_connector: MiddlewareConnector, /// Immutable driver capability and startup substrate state. transparent_tcp: TransparentTcpReloadState, @@ -3698,9 +3172,8 @@ struct PolicyPollLoopContext { openshell_supervisor_process::supervisor_session::ConfigApplyRequest, >, >, - /// Present for revision-2 sessions. The bootstrap already initialized - /// runtime state, so this seeds equality tracking and disables fetch-based - /// reconciliation for the current protocol. + /// The required stream bootstrap already initialized runtime state; this + /// seeds exact revision tracking for subsequent complete snapshots. initial_stream_snapshot: Option, } @@ -3759,9 +3232,9 @@ async fn install_builtin_middleware_registry(opa_engine: &OpaEngine) -> Result<( opa_engine.replace_middleware_registry(registry) } -/// Wait the configured poll interval, but never past the point at which an +/// Wait the configured refresh interval, but never past the point at which an /// installed extension credential must be rotated. -fn next_poll_delay( +fn next_refresh_delay( store: &openshell_extension_core::ExtensionCredentialStore, interval: Duration, ) -> Duration { @@ -3873,13 +3346,7 @@ struct PolicyValidationFailureDisposition { active_generation: u64, } -struct RejectedPolicyGeneration { - version: u32, - policy_hash: String, - validation_error: String, - configured_mode: PolicyValidationFailureMode, -} - +#[allow(dead_code)] enum GatewayRuntimeFailureDisposition { PolicyRejected { error: String, @@ -3927,30 +3394,6 @@ fn apply_gateway_runtime_reload_failure( } } -fn emit_transparent_tcp_expansion_rejection( - version: u32, - policy_hash: &str, - active_generation: u64, - error: &str, -) { - let message = format!( - "Transparent TCP policy expansion rejected; previous policy IS active [version:{version} active_generation:{active_generation} error:{error}]" - ); - ocsf_emit!( - ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::High) - .status(StatusId::Failure) - .state(StateId::Enabled, "retained_previous_policy") - .unmapped("candidate_version", serde_json::json!(version)) - .unmapped("candidate_policy_hash", serde_json::json!(policy_hash)) - .unmapped("previous_policy_active", serde_json::json!(true)) - .unmapped("active_generation", serde_json::json!(active_generation)) - .unmapped("validation_error", serde_json::json!(error)) - .message(message) - .build() - ); -} - fn apply_policy_validation_failure( engine: &OpaEngine, configured_mode: PolicyValidationFailureMode, @@ -3993,7 +3436,7 @@ fn policy_validation_failure_events( version: u32, policy_hash: &str, error: &str, -) -> [OcsfEvent; 2] { +) -> [openshell_ocsf::OcsfEvent; 2] { let previous_policy_state = if disposition.previous_policy_active { "IS active" } else { @@ -4080,6 +3523,30 @@ fn emit_policy_validation_failure( } } +fn emit_transparent_tcp_expansion_rejection( + version: u32, + policy_hash: &str, + active_generation: u64, + error: &str, +) { + let message = format!( + "Transparent TCP policy expansion rejected; previous policy IS active [version:{version} active_generation:{active_generation} error:{error}]" + ); + ocsf_emit!( + ConfigStateChangeBuilder::new(ocsf_ctx()) + .severity(SeverityId::High) + .status(StatusId::Failure) + .state(StateId::Enabled, "retained_previous_policy") + .unmapped("candidate_version", serde_json::json!(version)) + .unmapped("candidate_policy_hash", serde_json::json!(policy_hash)) + .unmapped("previous_policy_active", serde_json::json!(true)) + .unmapped("active_generation", serde_json::json!(active_generation)) + .unmapped("validation_error", serde_json::json!(error)) + .message(message) + .build() + ); +} + async fn receive_config_apply( receiver: &mut Option< tokio::sync::mpsc::Receiver< @@ -4104,6 +3571,7 @@ fn sandbox_config_revision( policy_version: snapshot.version, policy_source: snapshot.policy_source.into(), global_policy_version: snapshot.global_policy_version, + settings_revision: snapshot.settings_revision, }, ), ), @@ -4144,7 +3612,7 @@ fn config_apply_result( #[allow(clippy::too_many_arguments)] async fn apply_stream_config_request( - ctx: &PolicyPollLoopContext, + ctx: &StreamConfigLoopContext, client: &C, request: openshell_supervisor_process::supervisor_session::ConfigApplyRequest, current_config_revision: &mut u64, @@ -4245,7 +3713,7 @@ async fn apply_stream_config_request( } fn apply_stream_provider_snapshot( - ctx: &PolicyPollLoopContext, + ctx: &StreamConfigLoopContext, snapshot: openshell_core::proto::ProviderEnvironmentSnapshot, current_revision: &mut u64, ) -> openshell_core::proto::ConfigComponentApplyResult { @@ -4304,7 +3772,7 @@ fn apply_stream_provider_snapshot( #[allow(clippy::too_many_arguments)] async fn apply_stream_sandbox_snapshot( - ctx: &PolicyPollLoopContext, + ctx: &StreamConfigLoopContext, client: &C, snapshot: openshell_core::grpc_client::SettingsPollResult, current_config_revision: &mut u64, @@ -4522,699 +3990,85 @@ async fn apply_stream_sandbox_snapshot( } } -async fn run_policy_poll_loop(ctx: PolicyPollLoopContext) -> Result<()> { +async fn run_stream_config_loop(ctx: StreamConfigLoopContext) -> Result<()> { let client = openshell_core::grpc_client::CachedOpenShellClient::connect_with_credentials( &ctx.endpoint, ctx.extension_credentials.clone(), ) .await?; - run_policy_poll_loop_with_client(ctx, client).await + run_stream_config_loop_with_client(ctx, client).await } -async fn run_policy_poll_loop_with_client( - mut ctx: PolicyPollLoopContext, +async fn run_stream_config_loop_with_client( + mut ctx: StreamConfigLoopContext, client: C, ) -> Result<()> { - use openshell_core::proto::PolicySource; - use std::sync::atomic::Ordering; - let mut config_apply_rx = ctx.config_apply_rx.take(); - let (status_sender, status_receiver) = tokio::sync::mpsc::unbounded_channel(); - tokio::spawn(run_policy_status_reporter( - client.clone(), - ctx.sandbox_id.clone(), - status_receiver, - )); - - let initial_stream_snapshot = ctx.initial_stream_snapshot.take(); - let stream_authoritative = initial_stream_snapshot.is_some(); - let mut current_config_revision: u64 = initial_stream_snapshot - .as_ref() - .map_or(0, |snapshot| snapshot.config_revision); - let mut current_stream_sandbox_revision = initial_stream_snapshot - .as_ref() - .map(sandbox_config_revision) - .filter(|_| ctx.loaded_policy_origin.allows_gateway_policy_reload()); + + let initial_stream_snapshot = ctx + .initial_stream_snapshot + .take() + .ok_or_else(|| miette::miette!("supervisor stream bootstrap is required"))?; + let mut current_config_revision: u64 = initial_stream_snapshot.config_revision; + let mut current_stream_sandbox_revision = ctx + .loaded_policy_origin + .allows_gateway_policy_reload() + .then(|| sandbox_config_revision(&initial_stream_snapshot)); let mut current_provider_env_revision: u64 = ctx.provider_credentials.snapshot().revision; - let mut current_policy_version: u32 = initial_stream_snapshot - .as_ref() - .map_or(0, |snapshot| snapshot.version); - let mut current_policy_hash = initial_stream_snapshot - .as_ref() - .map_or_else(String::new, |snapshot| snapshot.policy_hash.clone()); + let mut current_policy_version: u32 = initial_stream_snapshot.version; + let mut current_policy_hash = initial_stream_snapshot.policy_hash.clone(); let mut current_middleware_services = initial_stream_snapshot - .as_ref() - .map_or_else(Vec::new, |snapshot| { - snapshot.supervisor_middleware_services.clone() - }); - let mut current_extension_authentication_enabled = initial_stream_snapshot - .as_ref() - .map_or(ctx.extension_authentication_enabled, |snapshot| { - snapshot.extension_authentication_enabled - }); + .supervisor_middleware_services + .clone(); + let mut current_extension_authentication_enabled = + initial_stream_snapshot.extension_authentication_enabled; let mut middleware_registry_status = ctx.middleware_registry_status; let mut current_settings: std::collections::HashMap< String, openshell_core::proto::EffectiveSetting, - > = initial_stream_snapshot - .as_ref() - .map_or_else(std::collections::HashMap::new, |snapshot| { - snapshot.settings.clone() - }); + > = initial_stream_snapshot.settings.clone(); let reloads_gateway_policy = ctx.loaded_policy_origin.allows_gateway_policy_reload(); - let mut last_failed_runtime_revision: Option = None; - let mut rejected_policy_generation: Option = None; let mut has_last_valid_policy = ctx.loaded_policy_origin.has_last_valid_policy(); - - // A first poll that does not match the policy already loaded into OPA must - // pass through the normal reconciliation path immediately. It must never - // seed the applied-state trackers before OPA actually loads it. - let mut pending_result = None; - - // Initialize revision from the first poll and acknowledge the initial - // policy revision the supervisor actually loaded. A mismatched result is - // reconciled below instead of being recorded as already applied. - if let Some(snapshot) = initial_stream_snapshot.as_ref() { + { + let snapshot = &initial_stream_snapshot; let _ = ctx.workspace_tx.send(snapshot.workspace.clone()); apply_ocsf_json_setting(&ctx.ocsf_enabled, &snapshot.settings); apply_ocsf_schema_version_setting(&ctx.ocsf_schema_version, &snapshot.settings); - } else { - match client.poll_settings(&ctx.sandbox_id).await { - Ok(result) => { - let _ = ctx.workspace_tx.send(client.workspace()); - match initial_poll_disposition(&ctx.loaded_policy_origin, &result) { - InitialPollDisposition::Acknowledge(candidate) => { - let stream_revision = sandbox_config_revision(&result); - apply_ocsf_json_setting(&ctx.ocsf_enabled, &result.settings); - apply_ocsf_schema_version_setting( - &ctx.ocsf_schema_version, - &result.settings, - ); - apply_agent_proposals_enabled( - &ctx.agent_proposals, - agent_proposals_enabled_from_settings(&result.settings), - "initial settings poll", - Some(candidate.config_revision), - ctx.sidecar_control_publisher.as_ref(), - skills::install_static_skills, - ); - current_config_revision = candidate.config_revision; - current_policy_version = candidate.version; - current_policy_hash.clone_from(&candidate.policy_hash); - current_middleware_services = result.supervisor_middleware_services; - current_extension_authentication_enabled = - result.extension_authentication_enabled; - current_settings = result.settings; - current_stream_sandbox_revision = Some(stream_revision); - enqueue_policy_status( - &status_sender, - PolicyStatusUpdate::initial_loaded(&candidate), - ); - debug!( - config_revision = current_config_revision, - "Settings poll: initial policy matches loaded revision" - ); - } - InitialPollDisposition::Reconcile => pending_result = Some(result), - InitialPollDisposition::TrackOnly => { - apply_ocsf_json_setting(&ctx.ocsf_enabled, &result.settings); - apply_ocsf_schema_version_setting( - &ctx.ocsf_schema_version, - &result.settings, - ); - apply_agent_proposals_enabled( - &ctx.agent_proposals, - agent_proposals_enabled_from_settings(&result.settings), - "initial settings poll", - Some(result.config_revision), - ctx.sidecar_control_publisher.as_ref(), - skills::install_static_skills, - ); - current_config_revision = result.config_revision; - current_policy_hash = result.policy_hash.clone(); - current_middleware_services = result.supervisor_middleware_services; - current_extension_authentication_enabled = - result.extension_authentication_enabled; - current_settings = result.settings; - debug!( - config_revision = current_config_revision, - "Settings poll: tracking gateway config while preserving local policy override" - ); - } - } - } - Err(e) => { - warn!(error = %e, "Settings poll: failed to fetch initial version, will retry"); - } - } } let interval = Duration::from_secs(ctx.interval_secs); loop { - if stream_authoritative { - let delay = next_poll_delay(&ctx.extension_credentials, interval); - tokio::select! { - request = receive_config_apply(&mut config_apply_rx) => { - let Some(request) = request else { - return Err(miette::miette!("stream configuration apply channel closed")); - }; - apply_stream_config_request( - &ctx, - &client, - request, - &mut current_config_revision, - &mut current_stream_sandbox_revision, - &mut current_provider_env_revision, - &mut current_policy_version, - &mut current_policy_hash, - &mut current_middleware_services, - &mut current_extension_authentication_enabled, - &mut middleware_registry_status, - &mut current_settings, - reloads_gateway_policy, - &mut has_last_valid_policy, - ).await; - } - () = tokio::time::sleep(delay) => { - if current_extension_authentication_enabled - && let Err(error) = client.refresh_installed_extension_credentials().await - { - warn!(error = %error, "Extension credential refresh failed"); - } - } - } - continue; - } - let result = if let Some(result) = pending_result.take() { - result - } else { - let delay = next_poll_delay(&ctx.extension_credentials, interval); - tokio::select! { - request = receive_config_apply(&mut config_apply_rx) => { - if let Some(request) = request { - apply_stream_config_request( - &ctx, - &client, - request, - &mut current_config_revision, - &mut current_stream_sandbox_revision, - &mut current_provider_env_revision, - &mut current_policy_version, - &mut current_policy_hash, - &mut current_middleware_services, - &mut current_extension_authentication_enabled, - &mut middleware_registry_status, - &mut current_settings, - reloads_gateway_policy, - &mut has_last_valid_policy, - ).await; - continue; - } - } - () = tokio::time::sleep(delay) => {} - } - match client.poll_settings(&ctx.sandbox_id).await { - Ok(result) => { - let _ = ctx.workspace_tx.send(client.workspace()); - result - } - Err(e) => { - debug!(error = %e, "Settings poll: server unreachable, will retry"); - if current_extension_authentication_enabled - && let Err(refresh_error) = - client.refresh_installed_extension_credentials().await - { - warn!( - error = %refresh_error, - "Settings poll: extension credential refresh failed while configuration was unavailable" - ); - } - continue; - } - } - }; - - // Reuse installed per-service credentials, rotating only when one is - // missing or due. Rotation happens on the existing gateway channel and - // updates slots in place, so it is independent of config revision and - // registry equality. - let middleware_credentials = if result.extension_authentication_enabled { - match client - .extension_credentials_for(&result.supervisor_middleware_services) - .await - { - Ok(credentials) => credentials, - Err(error) => { - warn!(error = %error, "Settings poll: extension credential refresh failed"); - std::collections::HashMap::new() - } - } - } else { - std::collections::HashMap::new() - }; - - let config_changed = result.config_revision != current_config_revision; - let provider_env_changed = result.provider_env_revision != current_provider_env_revision; - let policy_changed = result.policy_hash != current_policy_hash; - let extension_authentication_changed = - current_extension_authentication_enabled != result.extension_authentication_enabled; - let middleware_registry_changed = extension_authentication_changed - || middleware_registry_needs_rebuild( - middleware_registry_status, - ¤t_middleware_services, - &result.supervisor_middleware_services, - ); - // A valid candidate may intentionally restore byte-for-byte policy - // content that was active before a rejected update. Its hash then - // equals `current_policy_hash`, but the runtime is still quarantined - // and must reload (or it would remain deny-all indefinitely). - let recovering_rejected_policy = reloads_gateway_policy - && rejected_policy_generation - .as_ref() - .is_some_and(|rejected| rejected.policy_hash != result.policy_hash); - let policy_runtime_changed = recovering_rejected_policy - || extension_authentication_changed - || gateway_policy_runtime_needs_reconciliation( - reloads_gateway_policy, - ¤t_policy_hash, - &result.policy_hash, - ¤t_middleware_services, - &result.supervisor_middleware_services, - middleware_registry_status, - ); - // Recovery already has its own acknowledgement path below. Giving it - // precedence here prevents a restored last-known-good policy from - // also being acknowledged as an ordinary same-hash revision. - let unchanged_policy_revision = unchanged_policy_revision_candidate( - reloads_gateway_policy, - recovering_rejected_policy, - current_policy_version, - ¤t_policy_hash, - &result, - ); - let mut policy_runtime_reconciled = false; - - // A local policy override is not coupled to the gateway policy - // snapshot, so its service registry can still be reconciled alone. - // Gateway policy snapshots, however, must install policy and registry - // as one generation below. - if !reloads_gateway_policy { - reconcile_middleware_registry( - &ctx.opa_engine, - &ctx.middleware_connector, - MiddlewareRegistryReconciliation { - desired_services: &result.supervisor_middleware_services, - authentication: MiddlewareAuthentication { - credentials: middleware_credentials.clone(), - enabled: result.extension_authentication_enabled, - }, - registry_changed: middleware_registry_changed, - extension_credentials: &ctx.extension_credentials, - current_services: &mut current_middleware_services, - status: &mut middleware_registry_status, - }, - ) - .await; - if middleware_registry_status == MiddlewareRegistryStatus::Synchronized { - current_extension_authentication_enabled = result.extension_authentication_enabled; - } - } - - if !config_changed - && !provider_env_changed - && !policy_runtime_changed - && unchanged_policy_revision.is_none() - { - continue; - } - - if config_changed || provider_env_changed { - // Log which settings changed. - log_setting_changes(¤t_settings, &result.settings); - - // A posture change after a rejected update takes effect immediately. - // The compiled last-known-good engine remains available beneath a - // fail-closed quarantine, so an explicit retain_last_valid selection - // can reactivate it without accepting any part of the invalid policy. - if !policy_changed && let Some(rejected) = rejected_policy_generation.as_mut() { - let mode = result.policy_validation_failure_mode; - if mode != rejected.configured_mode { - let disposition = apply_policy_validation_failure( - &ctx.opa_engine, - mode, - has_last_valid_policy, - rejected.version, - &rejected.validation_error, - )?; - emit_policy_validation_failure( - &disposition, - rejected.version, - &rejected.policy_hash, - &rejected.validation_error, - ); - rejected.configured_mode = mode; - } - } - - ocsf_emit!(ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::Informational) - .status(StatusId::Success) - .state(StateId::Other, "detected") - .unmapped("old_config_revision", serde_json::json!(current_config_revision)) - .unmapped("new_config_revision", serde_json::json!(result.config_revision)) - .unmapped("policy_changed", serde_json::json!(policy_changed)) - .unmapped("provider_env_changed", serde_json::json!(provider_env_changed)) - .message(format!( - "Settings poll: config change detected [old_revision:{current_config_revision} new_revision:{} policy_changed:{policy_changed} provider_env_changed:{provider_env_changed}]", - result.config_revision - )) - .build()); - } - - if provider_env_changed { - match openshell_core::grpc_client::fetch_provider_environment( - &ctx.endpoint, - &ctx.sandbox_id, - ) - .await - { - Ok(env_result) => { - let provider_env_revision = env_result.provider_env_revision; - let install_result = ctx.provider_credentials.install_bound_environment( - provider_env_revision, - env_result.environment, - env_result.credential_expires_at_ms, - env_result.dynamic_credentials, - env_result.static_credential_bindings, - env_result.non_secret_environment_keys, - ); - if let Err(error) = install_result { - ocsf_emit!( - ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::High) - .status(StatusId::Failure) - .state(StateId::Disabled, "fail_closed") - .message(format!( - "Rejected provider environment refresh; static provider credentials were revoked; fetched dynamic token grants remain active: {error}" - )) - .build() - ); - } else { - let child_env = ctx.provider_credentials.child_env_with_gcp_resolved(); - let env_count = child_env.len(); - if let Some(publisher) = ctx.sidecar_control_publisher.as_ref() { - publisher - .publish_provider_env(provider_env_revision, child_env.clone()); - } - current_provider_env_revision = provider_env_revision; - ocsf_emit!( - ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::Informational) - .status(StatusId::Success) - .state(StateId::Enabled, "loaded") - .unmapped( - "provider_env_revision", - serde_json::json!(provider_env_revision) - ) - .message(format!( - "Provider environment refreshed [revision:{provider_env_revision} env_count:{env_count}]" - )) - .build() - ); - } - } - Err(e) => { - ctx.provider_credentials - .revoke_static_provider_environment(result.provider_env_revision); - warn!( - error = %e, - provider_env_revision = result.provider_env_revision, - "Settings poll: failed to refresh provider environment; static provider credentials were revoked; previous dynamic token grants remain active" - ); - ocsf_emit!( - ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::High) - .status(StatusId::Failure) - .state(StateId::Disabled, "fail_closed") - .message( - "Provider environment refresh failed; static provider credentials were revoked; previous dynamic token grants remain active" - ) - .build() - ); - } + let delay = next_refresh_delay(&ctx.extension_credentials, interval); + tokio::select! { + request = receive_config_apply(&mut config_apply_rx) => { + let Some(request) = request else { + return Err(miette::miette!("stream configuration apply channel closed")); + }; + apply_stream_config_request( + &ctx, + &client, + request, + &mut current_config_revision, + &mut current_stream_sandbox_revision, + &mut current_provider_env_revision, + &mut current_policy_version, + &mut current_policy_hash, + &mut current_middleware_services, + &mut current_extension_authentication_enabled, + &mut middleware_registry_status, + &mut current_settings, + reloads_gateway_policy, + &mut has_last_valid_policy, + ).await; } - } - - if policy_runtime_changed { - let pid = ctx.entrypoint_pid.load(Ordering::Acquire); - let runtime_result = reload_gateway_policy_runtime( - &ctx.opa_engine, - result.policy.as_ref(), - pid, - MiddlewareReloadContext { - desired_services: &result.supervisor_middleware_services, - authentication: &MiddlewareAuthentication { - credentials: middleware_credentials.clone(), - enabled: result.extension_authentication_enabled, - }, - registry_changed: middleware_registry_changed, - connector: &ctx.middleware_connector, - }, - ctx.transparent_tcp, - ) - .await; - - match runtime_result { - Ok(()) => { - policy_runtime_reconciled = true; - let policy = result - .policy - .as_ref() - .expect("successful runtime reload requires a policy payload"); - has_last_valid_policy = true; - rejected_policy_generation = None; - if policy_changed { - if let Some(policy_local_ctx) = ctx.policy_local_ctx.as_ref() { - policy_local_ctx.set_current_policy(policy.clone()).await; - } - if let Some(publisher) = ctx.sidecar_control_publisher.as_ref() { - publisher.publish_policy( - policy.clone(), - result.policy_hash.clone(), - result.config_revision, - ); - } - if result.global_policy_version > 0 { - ocsf_emit!(ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::Informational) - .status(StatusId::Success) - .state(StateId::Enabled, "loaded") - .unmapped("policy_hash", serde_json::json!(&result.policy_hash)) - .unmapped("global_version", serde_json::json!(result.global_policy_version)) - .message(format!( - "Policy reloaded successfully (global) [policy_hash:{} global_version:{}]", - result.policy_hash, - result.global_policy_version - )) - .build()); - } else { - ocsf_emit!( - ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::Informational) - .status(StatusId::Success) - .state(StateId::Enabled, "loaded") - .unmapped("policy_hash", serde_json::json!(&result.policy_hash)) - .message(format!( - "Policy reloaded successfully [policy_hash:{}]", - result.policy_hash - )) - .build() - ); - } - if result.version > 0 && result.policy_source == PolicySource::Sandbox { - enqueue_policy_status( - &status_sender, - PolicyStatusUpdate::loaded(result.version), - ); - current_policy_version = result.version; - } - } else if recovering_rejected_policy - && result.version > 0 - && result.policy_source == PolicySource::Sandbox - { - ocsf_emit!( - ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::Informational) - .status(StatusId::Success) - .state(StateId::Enabled, "loaded") - .unmapped("policy_hash", serde_json::json!(&result.policy_hash)) - .message(format!( - "Policy reloaded successfully and fail-closed quarantine cleared [policy_hash:{}]", - result.policy_hash - )) - .build() - ); - enqueue_policy_status( - &status_sender, - PolicyStatusUpdate::loaded(result.version), - ); - current_policy_version = result.version; - } - - if middleware_registry_changed { - ocsf_emit!(ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::Informational) - .status(StatusId::Success) - .state(StateId::Enabled, "loaded") - .unmapped( - "supervisor_middleware_service_count", - serde_json::json!(result.supervisor_middleware_services.len()) - ) - .message(format!( - "Supervisor policy runtime reloaded atomically [service_count:{}]", - result.supervisor_middleware_services.len() - )) - .build()); - } - - current_policy_hash.clone_from(&result.policy_hash); - current_middleware_services.clone_from(&result.supervisor_middleware_services); - current_extension_authentication_enabled = - result.extension_authentication_enabled; - retain_extension_credentials( - &ctx.extension_credentials, - &result.supervisor_middleware_services, - result.extension_authentication_enabled, - ); - middleware_registry_status = MiddlewareRegistryStatus::Synchronized; - last_failed_runtime_revision = None; - } - Err(failure) => { - let failed_revision = FailedRuntimeRevision::new( - result.config_revision, - &result.policy_hash, - &failure, - ); - if last_failed_runtime_revision.as_ref() != Some(&failed_revision) { - let failure_mode = result.policy_validation_failure_mode; - match apply_gateway_runtime_reload_failure( - &ctx.opa_engine, - failure, - failure_mode, - has_last_valid_policy, - result.version, - )? { - GatewayRuntimeFailureDisposition::PolicyRejected { - error, - disposition, - } => { - emit_policy_validation_failure( - &disposition, - result.version, - &result.policy_hash, - &error, - ); - rejected_policy_generation = Some(RejectedPolicyGeneration { - version: result.version, - policy_hash: result.policy_hash.clone(), - validation_error: error.clone(), - configured_mode: failure_mode, - }); - if policy_changed - && result.version > 0 - && result.policy_source == PolicySource::Sandbox - { - enqueue_policy_status( - &status_sender, - PolicyStatusUpdate::failed(result.version, error), - ); - } - } - GatewayRuntimeFailureDisposition::MiddlewareUnavailable { error } => { - ocsf_emit!(ConfigStateChangeBuilder::new(ocsf_ctx()) - .severity(SeverityId::Medium) - .status(StatusId::Failure) - .state(StateId::Other, "failed") - .unmapped("version", serde_json::json!(result.version)) - .unmapped("error", serde_json::json!(&error)) - .unmapped("previous_policy_active", serde_json::json!(true)) - .message(format!( - "Supervisor middleware registry unavailable, keeping last-known-good policy runtime active [version:{} error:{error}]", - result.version - )) - .build()); - } - GatewayRuntimeFailureDisposition::TransparentTcpExpansionRejected { - error, - active_generation, - } => { - emit_transparent_tcp_expansion_rejection( - result.version, - &result.policy_hash, - active_generation, - &error, - ); - if policy_changed - && result.version > 0 - && result.policy_source == PolicySource::Sandbox - { - enqueue_policy_status( - &status_sender, - PolicyStatusUpdate::failed(result.version, error), - ); - } - } - } - } - last_failed_runtime_revision = Some(failed_revision); - // Nothing was installed, so the registry status still - // describes the live registry. The retry is driven by the - // persisting hash/service-set mismatch (or an existing - // NeedsReconciliation), not by degrading the status here. + () = tokio::time::sleep(delay) => { + if current_extension_authentication_enabled + && let Err(error) = client.refresh_installed_extension_credentials().await + { + warn!(error = %error, "Extension credential refresh failed"); } } } - - if let Some(version) = unchanged_policy_revision_ready_to_ack( - unchanged_policy_revision, - policy_runtime_changed, - policy_runtime_reconciled, - ) { - enqueue_policy_status( - &status_sender, - PolicyStatusUpdate::unchanged_loaded(version, result.policy_hash.clone()), - ); - current_policy_version = version; - } - - // Apply OCSF JSON toggle from the `ocsf_json_enabled` setting. - apply_ocsf_json_setting(&ctx.ocsf_enabled, &result.settings); - apply_ocsf_schema_version_setting(&ctx.ocsf_schema_version, &result.settings); - - // Apply the agent-proposals feature toggle. On a false→true transition - // we lazily install the skill so a sandbox that started with the flag - // off picks up the surface without a recreate. We never uninstall on - // a true→false transition: stale skill content on disk is harmless - // because route_request and agent_next_steps both gate on the live - // shared flag, so the agent that reads the skill will see 404s and an - // empty `next_steps` array regardless. - apply_agent_proposals_enabled( - &ctx.agent_proposals, - agent_proposals_enabled_from_settings(&result.settings), - "settings poll", - Some(result.config_revision), - ctx.sidecar_control_publisher.as_ref(), - skills::install_static_skills, - ); - - if reloads_gateway_policy && (!policy_runtime_changed || policy_runtime_reconciled) { - current_stream_sandbox_revision = Some(sandbox_config_revision(&result)); - } - current_config_revision = result.config_revision; - if !reloads_gateway_policy { - current_policy_hash = result.policy_hash; - } - current_settings = result.settings; } } @@ -5393,6 +4247,7 @@ fn format_setting_value(es: &openshell_core::proto::EffectiveSetting) -> String #[cfg(test)] #[allow( + dead_code, clippy::needless_raw_string_hashes, clippy::iter_on_single_items, clippy::similar_names, @@ -5845,6 +4700,7 @@ network_policies: version, policy_hash: format!("hash-v{version}"), config_revision: u64::from(version) * 100, + settings_revision: 0, policy_source: source, settings: std::collections::HashMap::new(), global_policy_version: 0, @@ -5857,120 +4713,19 @@ network_policies: } #[derive(Clone)] - struct ScriptedPolicyGateway { - polls: Arc< - tokio::sync::Mutex< - tokio::sync::mpsc::UnboundedReceiver< - openshell_core::grpc_client::SettingsPollResult, - >, - >, - >, - reports: UnboundedSender<(u32, bool, String)>, - poll_calls: Arc, - } - - #[tonic::async_trait] - impl PolicyGatewayClient for ScriptedPolicyGateway { - async fn poll_settings( - &self, - _sandbox_id: &str, - ) -> Result { - self.poll_calls.fetch_add(1, Ordering::SeqCst); - self.polls - .lock() - .await - .recv() - .await - .ok_or_else(|| miette::miette!("scripted policy poll channel closed")) - } - - async fn report_policy_status( - &self, - _sandbox_id: &str, - version: u32, - loaded: bool, - error: &str, - ) -> Result<()> { - self.reports - .send((version, loaded, error.to_string())) - .map_err(|_| miette::miette!("scripted policy report channel closed")) - } - - fn workspace(&self) -> String { - "test-workspace".to_string() - } - } - - #[derive(Clone)] - struct CredentialRejectingPolicyGateway { - inner: ScriptedPolicyGateway, - credential_requests: Arc, - } + struct ScriptedPolicyGateway; #[tonic::async_trait] - impl PolicyGatewayClient for CredentialRejectingPolicyGateway { - async fn poll_settings( - &self, - sandbox_id: &str, - ) -> Result { - self.inner.poll_settings(sandbox_id).await - } - - async fn report_policy_status( - &self, - sandbox_id: &str, - version: u32, - loaded: bool, - error: &str, - ) -> Result<()> { - self.inner - .report_policy_status(sandbox_id, version, loaded, error) - .await - } - - async fn extension_credentials_for( - &self, - _services: &[openshell_core::proto::SupervisorMiddlewareService], - ) -> Result> - { - self.credential_requests.fetch_add(1, Ordering::SeqCst); - Err(miette::miette!( - "gateway extension authentication is unavailable" - )) - } - - fn workspace(&self) -> String { - self.inner.workspace() - } - } - - fn scripted_policy_gateway() -> ( - ScriptedPolicyGateway, - UnboundedSender, - tokio::sync::mpsc::UnboundedReceiver<(u32, bool, String)>, - ) { - let (poll_tx, poll_rx) = tokio::sync::mpsc::unbounded_channel(); - let (report_tx, report_rx) = tokio::sync::mpsc::unbounded_channel(); - ( - ScriptedPolicyGateway { - polls: Arc::new(tokio::sync::Mutex::new(poll_rx)), - reports: report_tx, - poll_calls: Arc::new(AtomicUsize::new(0)), - }, - poll_tx, - report_rx, - ) - } + impl PolicyGatewayClient for ScriptedPolicyGateway {} fn policy_poll_test_context( opa_engine: Arc, loaded_policy_origin: LoadedPolicyOrigin, middleware_connector: MiddlewareConnector, - ) -> PolicyPollLoopContext { + ) -> StreamConfigLoopContext { let (workspace_tx, _workspace_rx) = tokio::sync::watch::channel(String::new()); - PolicyPollLoopContext { + StreamConfigLoopContext { endpoint: String::new(), - sandbox_id: "sandbox-test".to_string(), opa_engine, loaded_policy_origin, entrypoint_pid: Arc::new(AtomicU32::new(0)), @@ -5987,7 +4742,6 @@ network_policies: sidecar_control_publisher: None, workspace_tx, extension_credentials: openshell_extension_core::ExtensionCredentialStore::new(), - extension_authentication_enabled: false, middleware_connector, transparent_tcp: TransparentTcpReloadState::default(), config_apply_rx: None, @@ -6018,7 +4772,7 @@ network_policies: let mut candidate = settings_poll_result(Some(proto_tcp_policy_fixture()), 2, PolicySource::Sandbox); candidate.policy_validation_failure_mode = PolicyValidationFailureMode::FailClosed; - let (client, _polls, _reports) = scripted_policy_gateway(); + let client = ScriptedPolicyGateway; let initial_revision = sandbox_config_revision(&initial); let initial_generation = engine.current_generation(); let result = apply_stream_sandbox_snapshot( @@ -6067,7 +4821,7 @@ network_policies: LoadedPolicyOrigin::LocalOverride, default_middleware_connector(), ); - let (client, _polls, _reports) = scripted_policy_gateway(); + let client = ScriptedPolicyGateway; let mut current_config_revision = 0; let mut current_revision = None; let mut version = 0; @@ -6174,7 +4928,7 @@ network_policies: }, default_middleware_connector(), ); - let (client, _polls, _reports) = scripted_policy_gateway(); + let client = ScriptedPolicyGateway; let mut config_revision = initial.config_revision; let mut stream_revision = Some(sandbox_config_revision(&initial)); let mut policy_version = initial.version; @@ -6212,7 +4966,7 @@ network_policies: } #[tokio::test] - async fn revision_two_stream_never_polls_gateway_settings() { + async fn revision_three_stream_applies_provider_update() { let initial = settings_poll_result( Some(proto_policy_fixture()), 1, @@ -6231,10 +4985,9 @@ network_policies: ctx.initial_stream_snapshot = Some(initial); let (config_apply_tx, config_apply_rx) = tokio::sync::mpsc::channel(1); ctx.config_apply_rx = Some(config_apply_rx); - let (client, _polls, _reports) = scripted_policy_gateway(); - let poll_calls = Arc::clone(&client.poll_calls); + let client = ScriptedPolicyGateway; - let handle = tokio::spawn(run_policy_poll_loop_with_client(ctx, client)); + let handle = tokio::spawn(run_stream_config_loop_with_client(ctx, client)); let (response_tx, response_rx) = tokio::sync::oneshot::channel(); config_apply_tx .send( @@ -6261,7 +5014,6 @@ network_policies: .expect("stream update timed out") .expect("stream update responder stopped"); - assert_eq!(poll_calls.load(Ordering::SeqCst), 0); handle.abort(); } @@ -6298,7 +5050,7 @@ network_policies: }, default_middleware_connector(), ); - let (client, _polls, _reports) = scripted_policy_gateway(); + let client = ScriptedPolicyGateway; let mut config_revision = initial.config_revision; let initial_revision = sandbox_config_revision(&initial); let mut stream_revision = Some(initial_revision); @@ -6337,465 +5089,6 @@ network_policies: ); } - async fn expect_policy_report( - reports: &mut tokio::sync::mpsc::UnboundedReceiver<(u32, bool, String)>, - version: u32, - ) { - let report = timeout(Duration::from_secs(1), reports.recv()) - .await - .expect("policy report timed out") - .expect("policy reporter stopped"); - assert_eq!(report, (version, true, String::new())); - } - - async fn expect_no_policy_report( - reports: &mut tokio::sync::mpsc::UnboundedReceiver<(u32, bool, String)>, - ) { - assert!( - timeout(Duration::from_millis(50), reports.recv()) - .await - .is_err(), - "unexpected policy status report" - ); - } - - #[tokio::test] - async fn same_hash_poll_revision_is_acknowledged_once_without_opa_reload() { - let mut v1 = settings_poll_result( - Some(proto_policy_fixture()), - 1, - openshell_core::proto::PolicySource::Sandbox, - ); - v1.policy_hash = "same-policy".to_string(); - let mut v2 = v1.clone(); - v2.version = 2; - v2.config_revision = 200; - - let engine = - Arc::new(OpaEngine::from_proto(&proto_policy_fixture()).expect("build OPA engine")); - let loaded_revision = LoadedPolicyRevision::from_snapshot(&v1); - let ctx = policy_poll_test_context( - engine.clone(), - LoadedPolicyOrigin::Gateway { - revision: Some(loaded_revision), - has_last_valid_policy: true, - }, - default_middleware_connector(), - ); - let (client, polls, mut reports) = scripted_policy_gateway(); - polls.send(v1).unwrap(); - - let handle = tokio::spawn(run_policy_poll_loop_with_client(ctx, client)); - expect_policy_report(&mut reports, 1).await; - - polls.send(v2.clone()).unwrap(); - expect_policy_report(&mut reports, 2).await; - polls.send(v2).unwrap(); - expect_no_policy_report(&mut reports).await; - - assert_eq!( - engine.current_generation(), - 0, - "same-hash acknowledgement must not reload OPA" - ); - handle.abort(); - } - - #[tokio::test] - async fn poll_rejects_first_tcp_expansion_and_reports_previous_policy_active() { - let v1 = settings_poll_result( - Some(proto_policy_fixture()), - 1, - openshell_core::proto::PolicySource::Sandbox, - ); - let v2 = settings_poll_result( - Some(proto_tcp_policy_fixture()), - 2, - openshell_core::proto::PolicySource::Sandbox, - ); - let engine = - Arc::new(OpaEngine::from_proto(&proto_policy_fixture()).expect("build OPA engine")); - let active_generation = engine.current_generation(); - let loaded_revision = LoadedPolicyRevision::from_snapshot(&v1); - let mut ctx = policy_poll_test_context( - engine.clone(), - LoadedPolicyOrigin::Gateway { - revision: Some(loaded_revision), - has_last_valid_policy: true, - }, - default_middleware_connector(), - ); - ctx.transparent_tcp = TransparentTcpReloadState { - capable: true, - substrate_ready: false, - }; - let (client, polls, mut reports) = scripted_policy_gateway(); - polls.send(v1).unwrap(); - - let handle = tokio::spawn(run_policy_poll_loop_with_client(ctx, client)); - expect_policy_report(&mut reports, 1).await; - polls.send(v2).unwrap(); - let report = timeout(Duration::from_secs(1), reports.recv()) - .await - .expect("TCP rejection report timed out") - .expect("policy reporter stopped"); - - assert_eq!(report.0, 2); - assert!(!report.1); - assert!(report.2.contains("recreate the sandbox"), "{}", report.2); - assert!(report.2.contains("previous policy remains active")); - assert_eq!(engine.current_generation(), active_generation); - assert!(engine.fail_closed_reason().is_none()); - handle.abort(); - } - - #[tokio::test] - async fn same_hash_ack_waits_for_failed_middleware_reconciliation_and_retries_once() { - let mut v1 = settings_poll_result( - Some(proto_policy_fixture()), - 1, - openshell_core::proto::PolicySource::Sandbox, - ); - v1.policy_hash = "same-policy".to_string(); - let mut v2 = v1.clone(); - v2.version = 2; - v2.config_revision = 200; - v2.supervisor_middleware_services = - vec![openshell_core::proto::SupervisorMiddlewareService { - name: "scripted-guard".to_string(), - grpc_endpoint: "http://scripted.invalid".to_string(), - ..Default::default() - }]; - - let connector_attempts = Arc::new(AtomicUsize::new(0)); - let (attempt_tx, mut attempt_rx) = tokio::sync::mpsc::unbounded_channel(); - let middleware_connector: MiddlewareConnector = { - let connector_attempts = connector_attempts.clone(); - Arc::new(move |_services, _authentication| { - let attempt = connector_attempts.fetch_add(1, Ordering::SeqCst) + 1; - attempt_tx.send(attempt).unwrap(); - Box::pin(async move { - if attempt == 1 { - Err(miette::miette!("scripted middleware connection failure")) - } else { - connect_middleware_registry(&[], &MiddlewareAuthentication::default()).await - } - }) - }) - }; - - let engine = - Arc::new(OpaEngine::from_proto(&proto_policy_fixture()).expect("build OPA engine")); - let loaded_revision = LoadedPolicyRevision::from_snapshot(&v1); - let ctx = policy_poll_test_context( - engine.clone(), - LoadedPolicyOrigin::Gateway { - revision: Some(loaded_revision), - has_last_valid_policy: true, - }, - middleware_connector, - ); - let (client, polls, mut reports) = scripted_policy_gateway(); - polls.send(v1).unwrap(); - - let handle = tokio::spawn(run_policy_poll_loop_with_client(ctx, client)); - expect_policy_report(&mut reports, 1).await; - - polls.send(v2.clone()).unwrap(); - assert_eq!( - timeout(Duration::from_secs(1), attempt_rx.recv()) - .await - .unwrap(), - Some(1) - ); - expect_no_policy_report(&mut reports).await; - assert_eq!(engine.current_generation(), 0); - - polls.send(v2.clone()).unwrap(); - assert_eq!( - timeout(Duration::from_secs(1), attempt_rx.recv()) - .await - .unwrap(), - Some(2) - ); - expect_policy_report(&mut reports, 2).await; - assert_eq!(engine.current_generation(), 1); - - polls.send(v2).unwrap(); - expect_no_policy_report(&mut reports).await; - assert_eq!(connector_attempts.load(Ordering::SeqCst), 2); - handle.abort(); - } - - #[tokio::test] - async fn no_signer_capability_uses_legacy_middleware_connector_without_credentials() { - let mut v1 = settings_poll_result( - Some(proto_policy_fixture()), - 1, - openshell_core::proto::PolicySource::Sandbox, - ); - v1.policy_hash = "same-policy".to_string(); - let mut v2 = v1.clone(); - v2.version = 2; - v2.config_revision = 200; - v2.supervisor_middleware_services = - vec![openshell_core::proto::SupervisorMiddlewareService { - name: "legacy-guard".to_string(), - grpc_endpoint: "http://legacy.invalid".to_string(), - ..Default::default() - }]; - assert!(!v2.extension_authentication_enabled); - - let (inner, polls, mut reports) = scripted_policy_gateway(); - let credential_requests = Arc::new(AtomicUsize::new(0)); - let client = CredentialRejectingPolicyGateway { - inner, - credential_requests: credential_requests.clone(), - }; - let (connector_tx, mut connector_rx) = tokio::sync::mpsc::unbounded_channel(); - let connector: MiddlewareConnector = Arc::new(move |_services, authentication| { - connector_tx - .send((authentication.credentials.len(), authentication.enabled)) - .unwrap(); - Box::pin(async move { - connect_middleware_registry(&[], &MiddlewareAuthentication::default()).await - }) - }); - let engine = - Arc::new(OpaEngine::from_proto(&proto_policy_fixture()).expect("build OPA engine")); - let loaded_revision = LoadedPolicyRevision::from_snapshot(&v1); - let ctx = policy_poll_test_context( - engine, - LoadedPolicyOrigin::Gateway { - revision: Some(loaded_revision), - has_last_valid_policy: true, - }, - connector, - ); - - polls.send(v1).unwrap(); - let handle = tokio::spawn(run_policy_poll_loop_with_client(ctx, client)); - expect_policy_report(&mut reports, 1).await; - polls.send(v2).unwrap(); - assert_eq!( - timeout(Duration::from_secs(1), connector_rx.recv()) - .await - .unwrap(), - Some((0, false)) - ); - expect_policy_report(&mut reports, 2).await; - assert_eq!(credential_requests.load(Ordering::SeqCst), 0); - handle.abort(); - } - - #[tokio::test] - async fn enabled_extension_authentication_keeps_credential_failure_fail_closed() { - let mut v1 = settings_poll_result( - Some(proto_policy_fixture()), - 1, - openshell_core::proto::PolicySource::Sandbox, - ); - v1.policy_hash = "same-policy".to_string(); - let mut v2 = v1.clone(); - v2.version = 2; - v2.config_revision = 200; - v2.extension_authentication_enabled = true; - v2.supervisor_middleware_services = - vec![openshell_core::proto::SupervisorMiddlewareService { - name: "authenticated-guard".to_string(), - grpc_endpoint: "https://guard.invalid".to_string(), - ..Default::default() - }]; - - let (inner, polls, mut reports) = scripted_policy_gateway(); - let credential_requests = Arc::new(AtomicUsize::new(0)); - let client = CredentialRejectingPolicyGateway { - inner, - credential_requests: credential_requests.clone(), - }; - let (connector_tx, mut connector_rx) = tokio::sync::mpsc::unbounded_channel(); - let connector: MiddlewareConnector = Arc::new(move |_services, authentication| { - connector_tx - .send((authentication.credentials.len(), authentication.enabled)) - .unwrap(); - Box::pin(async move { - if authentication.enabled && authentication.credentials.is_empty() { - Err(miette::miette!( - "missing authenticated middleware credential" - )) - } else { - connect_middleware_registry(&[], &authentication).await - } - }) - }); - let engine = - Arc::new(OpaEngine::from_proto(&proto_policy_fixture()).expect("build OPA engine")); - let loaded_revision = LoadedPolicyRevision::from_snapshot(&v1); - let ctx = policy_poll_test_context( - engine, - LoadedPolicyOrigin::Gateway { - revision: Some(loaded_revision), - has_last_valid_policy: true, - }, - connector, - ); - - polls.send(v1).unwrap(); - let handle = tokio::spawn(run_policy_poll_loop_with_client(ctx, client)); - expect_policy_report(&mut reports, 1).await; - polls.send(v2).unwrap(); - assert_eq!( - timeout(Duration::from_secs(1), connector_rx.recv()) - .await - .unwrap(), - Some((0, true)) - ); - expect_no_policy_report(&mut reports).await; - assert_eq!(credential_requests.load(Ordering::SeqCst), 1); - handle.abort(); - } - - async fn assert_poll_does_not_use_same_hash_acknowledgement( - initial: openshell_core::grpc_client::SettingsPollResult, - next: openshell_core::grpc_client::SettingsPollResult, - origin: LoadedPolicyOrigin, - initial_report: Option, - ) { - let engine = - Arc::new(OpaEngine::from_proto(&proto_policy_fixture()).expect("build OPA engine")); - let ctx = policy_poll_test_context(engine.clone(), origin, default_middleware_connector()); - let (client, polls, mut reports) = scripted_policy_gateway(); - polls.send(initial).unwrap(); - let handle = tokio::spawn(run_policy_poll_loop_with_client(ctx, client)); - - if let Some(version) = initial_report { - expect_policy_report(&mut reports, version).await; - } else { - expect_no_policy_report(&mut reports).await; - } - - polls.send(next).unwrap(); - expect_no_policy_report(&mut reports).await; - assert_eq!( - engine.current_generation(), - 0, - "negative same-hash scope must not reload OPA" - ); - handle.abort(); - } - - #[tokio::test] - async fn same_hash_ack_poll_loop_rejects_local_global_empty_equal_and_older_scopes() { - let mut sandbox_v1 = settings_poll_result( - Some(proto_policy_fixture()), - 1, - openshell_core::proto::PolicySource::Sandbox, - ); - sandbox_v1.policy_hash = "same-policy".to_string(); - let loaded_v1 = LoadedPolicyRevision::from_snapshot(&sandbox_v1); - let mut sandbox_v2 = sandbox_v1.clone(); - sandbox_v2.version = 2; - sandbox_v2.config_revision = 200; - - assert_poll_does_not_use_same_hash_acknowledgement( - sandbox_v1.clone(), - sandbox_v2.clone(), - LoadedPolicyOrigin::LocalOverride, - None, - ) - .await; - - let mut global_v2 = sandbox_v2.clone(); - global_v2.policy_source = openshell_core::proto::PolicySource::Global; - assert_poll_does_not_use_same_hash_acknowledgement( - sandbox_v1.clone(), - global_v2, - LoadedPolicyOrigin::Gateway { - revision: Some(loaded_v1.clone()), - has_last_valid_policy: true, - }, - Some(1), - ) - .await; - - let mut empty_v1 = sandbox_v1.clone(); - empty_v1.policy_hash.clear(); - let empty_loaded = LoadedPolicyRevision::from_snapshot(&empty_v1); - let mut empty_v2 = sandbox_v2.clone(); - empty_v2.policy_hash.clear(); - assert_poll_does_not_use_same_hash_acknowledgement( - empty_v1, - empty_v2, - LoadedPolicyOrigin::Gateway { - revision: Some(empty_loaded), - has_last_valid_policy: true, - }, - Some(1), - ) - .await; - - assert_poll_does_not_use_same_hash_acknowledgement( - sandbox_v1.clone(), - sandbox_v1.clone(), - LoadedPolicyOrigin::Gateway { - revision: Some(loaded_v1.clone()), - has_last_valid_policy: true, - }, - Some(1), - ) - .await; - - let loaded_v2 = LoadedPolicyRevision::from_snapshot(&sandbox_v2); - assert_poll_does_not_use_same_hash_acknowledgement( - sandbox_v2, - sandbox_v1, - LoadedPolicyOrigin::Gateway { - revision: Some(loaded_v2), - has_last_valid_policy: true, - }, - Some(2), - ) - .await; - } - - #[tokio::test] - async fn changed_hash_poll_uses_normal_opa_reload_and_status_path() { - let v1 = settings_poll_result( - Some(proto_policy_fixture()), - 1, - openshell_core::proto::PolicySource::Sandbox, - ); - let v2 = settings_poll_result( - Some(proto_policy_fixture()), - 2, - openshell_core::proto::PolicySource::Sandbox, - ); - let loaded_revision = LoadedPolicyRevision::from_snapshot(&v1); - let engine = - Arc::new(OpaEngine::from_proto(&proto_policy_fixture()).expect("build OPA engine")); - let ctx = policy_poll_test_context( - engine.clone(), - LoadedPolicyOrigin::Gateway { - revision: Some(loaded_revision), - has_last_valid_policy: true, - }, - default_middleware_connector(), - ); - let (client, polls, mut reports) = scripted_policy_gateway(); - polls.send(v1).unwrap(); - let handle = tokio::spawn(run_policy_poll_loop_with_client(ctx, client)); - - expect_policy_report(&mut reports, 1).await; - polls.send(v2).unwrap(); - expect_policy_report(&mut reports, 2).await; - assert_eq!( - engine.current_generation(), - 1, - "changed policy content must still reload OPA" - ); - handle.abort(); - } - #[tokio::test] async fn failed_external_startup_registry_build_preserves_installed_builtins() { let engine = OpaEngine::from_proto(&proto_policy_fixture()).expect("build OPA engine"); @@ -7086,224 +5379,6 @@ network_policies: )); } - #[test] - fn initial_ack_candidate_matches_sandbox_revision() { - let canonical = settings_poll_result( - Some(proto_policy_fixture()), - 2, - openshell_core::proto::PolicySource::Sandbox, - ); - let loaded = LoadedPolicyRevision::from_snapshot(&canonical); - - let ack = initial_policy_ack_candidate(Some(&loaded), &canonical) - .expect("sandbox-sourced matching revision should be acknowledged"); - - assert_eq!(ack.version, 2); - assert_eq!(ack.policy_hash, "hash-v2"); - assert_eq!(ack.config_revision, 200); - } - - #[test] - fn initial_ack_candidate_ignores_global_policy() { - let canonical = settings_poll_result( - Some(proto_policy_fixture()), - 1, - openshell_core::proto::PolicySource::Global, - ); - let loaded = LoadedPolicyRevision::from_snapshot(&canonical); - - assert!(initial_policy_ack_candidate(Some(&loaded), &canonical).is_none()); - } - - #[test] - fn initial_ack_candidate_ignores_version_zero() { - let canonical = settings_poll_result( - Some(proto_policy_fixture()), - 0, - openshell_core::proto::PolicySource::Sandbox, - ); - let loaded = LoadedPolicyRevision::from_snapshot(&canonical); - - assert!(initial_policy_ack_candidate(Some(&loaded), &canonical).is_none()); - } - - #[test] - fn initial_ack_candidate_ignores_local_file_mode() { - // Local-file mode retains no proto policy, so there is nothing to - // acknowledge to the gateway. - let canonical = settings_poll_result( - Some(proto_policy_fixture()), - 1, - openshell_core::proto::PolicySource::Sandbox, - ); - - assert!(initial_policy_ack_candidate(None, &canonical).is_none()); - } - - #[test] - fn initial_ack_candidate_rejects_mismatched_identity() { - let loaded_snapshot = settings_poll_result( - Some(proto_policy_fixture()), - 1, - openshell_core::proto::PolicySource::Sandbox, - ); - let loaded = LoadedPolicyRevision::from_snapshot(&loaded_snapshot); - let canonical = settings_poll_result( - Some(proto_policy_fixture()), - 2, - openshell_core::proto::PolicySource::Sandbox, - ); - - assert!(initial_policy_ack_candidate(Some(&loaded), &canonical).is_none()); - } - - #[test] - fn initial_poll_reconciles_provider_composition_that_was_not_loaded() { - let loaded_snapshot = settings_poll_result( - Some(proto_policy_fixture()), - 1, - openshell_core::proto::PolicySource::Sandbox, - ); - let loaded = LoadedPolicyRevision::from_snapshot(&loaded_snapshot); - let mut newer = proto_policy_fixture(); - newer.network_policies.insert( - "_provider_work_github".to_string(), - openshell_core::proto::NetworkPolicyRule::default(), - ); - let canonical = - settings_poll_result(Some(newer), 1, openshell_core::proto::PolicySource::Sandbox); - let canonical = openshell_core::grpc_client::SettingsPollResult { - policy_hash: "hash-provider-change".to_string(), - config_revision: loaded.config_revision + 1, - ..canonical - }; - - assert_eq!( - initial_poll_disposition( - &LoadedPolicyOrigin::Gateway { - revision: Some(loaded), - has_last_valid_policy: true, - }, - &canonical, - ), - InitialPollDisposition::Reconcile - ); - } - - #[test] - fn initial_poll_tracks_local_override_without_reconciliation() { - let canonical = settings_poll_result( - Some(proto_policy_fixture()), - 2, - openshell_core::proto::PolicySource::Sandbox, - ); - - assert_eq!( - initial_poll_disposition(&LoadedPolicyOrigin::LocalOverride, &canonical), - InitialPollDisposition::TrackOnly - ); - assert!(!LoadedPolicyOrigin::LocalOverride.allows_gateway_policy_reload()); - } - - #[test] - fn initial_poll_reconciles_unbound_gateway_policy() { - let canonical = settings_poll_result( - Some(proto_policy_fixture()), - 2, - openshell_core::proto::PolicySource::Sandbox, - ); - let origin = LoadedPolicyOrigin::Gateway { - revision: None, - has_last_valid_policy: true, - }; - - assert_eq!( - initial_poll_disposition(&origin, &canonical), - InitialPollDisposition::Reconcile - ); - assert!(origin.allows_gateway_policy_reload()); - } - - #[test] - fn unchanged_sandbox_policy_revision_candidate_is_strictly_scoped() { - let sandbox_result = openshell_core::grpc_client::SettingsPollResult { - policy_hash: "same-policy".to_string(), - ..settings_poll_result( - Some(proto_policy_fixture()), - 2, - openshell_core::proto::PolicySource::Sandbox, - ) - }; - - assert_eq!( - unchanged_policy_revision_candidate(true, false, 1, "same-policy", &sandbox_result), - Some(2) - ); - assert_eq!( - unchanged_policy_revision_candidate(true, false, 2, "same-policy", &sandbox_result), - None - ); - assert_eq!( - unchanged_policy_revision_candidate( - true, - false, - 1, - "different-policy", - &sandbox_result, - ), - None - ); - assert_eq!( - unchanged_policy_revision_candidate(false, false, 1, "same-policy", &sandbox_result), - None - ); - assert_eq!( - unchanged_policy_revision_candidate(true, false, 1, "", &sandbox_result), - None - ); - assert_eq!( - unchanged_policy_revision_candidate(true, true, 1, "same-policy", &sandbox_result), - None - ); - - let global_result = openshell_core::grpc_client::SettingsPollResult { - policy_hash: "same-policy".to_string(), - ..settings_poll_result( - Some(proto_policy_fixture()), - 2, - openshell_core::proto::PolicySource::Global, - ) - }; - assert_eq!( - unchanged_policy_revision_candidate(true, false, 1, "same-policy", &global_result), - None - ); - } - - #[test] - fn unchanged_policy_revision_waits_for_required_runtime_reconciliation() { - assert_eq!( - unchanged_policy_revision_ready_to_ack(Some(2), false, false), - Some(2), - "a same-hash revision needs no OPA reload" - ); - assert_eq!( - unchanged_policy_revision_ready_to_ack(Some(2), true, false), - None, - "failed runtime reconciliation must keep the revision pending" - ); - assert_eq!( - unchanged_policy_revision_ready_to_ack(Some(2), true, true), - Some(2), - "successful runtime reconciliation permits acknowledgement" - ); - assert_eq!( - unchanged_policy_revision_ready_to_ack(None, false, true), - None, - "runtime success cannot manufacture a revision candidate" - ); - } - #[test] fn credential_gating_unavailable_for_local_override_with_credentials() { assert!(credential_gating_unavailable( @@ -7338,21 +5413,6 @@ network_policies: )); } - #[test] - fn policy_status_outbox_preserves_all_revision_order() { - let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel(); - for version in 1..=128 { - enqueue_policy_status(&sender, PolicyStatusUpdate::loaded(version)); - } - - for version in 1..=128 { - assert_eq!( - receiver.try_recv().unwrap(), - PolicyStatusUpdate::loaded(version) - ); - } - } - #[test] fn settings_snapshot_carries_workspace_for_policy_sync() { let mut snapshot = settings_poll_result( diff --git a/crates/openshell-sdk/tests/client_mock.rs b/crates/openshell-sdk/tests/client_mock.rs index 549f715d9b..89ae99fa1d 100644 --- a/crates/openshell-sdk/tests/client_mock.rs +++ b/crates/openshell-sdk/tests/client_mock.rs @@ -656,6 +656,17 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("unused")) } + #[allow(unused_qualifications)] + async fn get_config_update_operation( + &self, + _request: tonic::Request, + ) -> Result< + tonic::Response, + tonic::Status, + > { + Err(tonic::Status::unimplemented("unused")) + } + async fn update_config( &self, _: tonic::Request, @@ -686,15 +697,6 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("unused")) } - async fn get_sandbox_provider_environment( - &self, - _: tonic::Request, - ) -> Result, Status> { - Ok(Response::new( - proto::GetSandboxProviderEnvironmentResponse::default(), - )) - } - async fn get_sandbox_logs( &self, _: tonic::Request, diff --git a/crates/openshell-server/migrations/postgres/009_config_update_operations.sql b/crates/openshell-server/migrations/postgres/009_config_update_operations.sql new file mode 100644 index 0000000000..8c6850ee62 --- /dev/null +++ b/crates/openshell-server/migrations/postgres/009_config_update_operations.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS sandbox_config_fences ( + sandbox_id TEXT PRIMARY KEY REFERENCES objects(id) ON DELETE CASCADE +); + +ALTER TABLE objects + ADD COLUMN IF NOT EXISTS next_attempt_at_ms BIGINT; + +UPDATE objects +SET next_attempt_at_ms = updated_at_ms +WHERE object_type = 'config_update_operation' + AND next_attempt_at_ms IS NULL; + +CREATE INDEX IF NOT EXISTS objects_type_status_due_idx + ON objects (object_type, status, next_attempt_at_ms, id); diff --git a/crates/openshell-server/migrations/sqlite/009_config_update_operations.sql b/crates/openshell-server/migrations/sqlite/009_config_update_operations.sql new file mode 100644 index 0000000000..3d10614058 --- /dev/null +++ b/crates/openshell-server/migrations/sqlite/009_config_update_operations.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS sandbox_config_fences ( + sandbox_id TEXT PRIMARY KEY REFERENCES objects(id) ON DELETE CASCADE +); + +ALTER TABLE objects + ADD COLUMN next_attempt_at_ms INTEGER; + +UPDATE objects +SET next_attempt_at_ms = updated_at_ms +WHERE object_type = 'config_update_operation' + AND next_attempt_at_ms IS NULL; + +CREATE INDEX IF NOT EXISTS objects_type_status_due_idx + ON objects (object_type, status, next_attempt_at_ms, id); diff --git a/crates/openshell-server/proto/storage.proto b/crates/openshell-server/proto/storage.proto index 851f260032..7e01813326 100644 --- a/crates/openshell-server/proto/storage.proto +++ b/crates/openshell-server/proto/storage.proto @@ -89,6 +89,25 @@ message StoredConfigComponentObservation { string sanitized_error = 9; } +// Durable, non-secret progress for a sandbox-scoped desired-state mutation. +// The target_* fields let a recovery worker bind the exact effective snapshot +// revision after the desired-state transaction commits. +message StoredConfigUpdateOperation { + openshell.datamodel.v1.ObjectMeta metadata = 1; + openshell.v1.ConfigUpdateOperation operation = 2; + uint32 target_policy_version = 3; + uint64 target_settings_revision = 4; + openshell.v1.SandboxPhase initial_phase = 5; + string idempotency_key = 6; + uint32 attempt_count = 7; + int64 next_attempt_at_ms = 8; + uint32 response_policy_version = 9; + string response_policy_hash = 10; + uint64 response_settings_revision = 11; + bool response_deleted = 12; + map response_annotations = 13; +} + // Stored payload for a policy revision row in the generic objects table. message PolicyRevisionPayload { // Serialized policy contents. diff --git a/crates/openshell-server/src/auth/method_authz.rs b/crates/openshell-server/src/auth/method_authz.rs index 35ff82e925..92beaa8468 100644 --- a/crates/openshell-server/src/auth/method_authz.rs +++ b/crates/openshell-server/src/auth/method_authz.rs @@ -129,9 +129,6 @@ mod tests { "/openshell.v1.OpenShell/ReportPolicyStatus" )); assert!(!is_user_callable("/openshell.v1.OpenShell/PushSandboxLogs")); - assert!(!is_user_callable( - "/openshell.v1.OpenShell/GetSandboxProviderEnvironment" - )); assert!(!is_user_callable( "/openshell.v1.OpenShell/SubmitPolicyAnalysis" )); diff --git a/crates/openshell-server/src/config_delivery.rs b/crates/openshell-server/src/config_delivery.rs index f38e2dd6b3..515ccd0152 100644 --- a/crates/openshell-server/src/config_delivery.rs +++ b/crates/openshell-server/src/config_delivery.rs @@ -26,11 +26,6 @@ use crate::supervisor_session::SupervisorSessionRegistry; /// 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. -pub const OPTIONAL_CONFIG_BOOTSTRAP_BUILD_TIMEOUT: Duration = Duration::from_secs(1); -// Stage 2 supervisors apply the bootstrap directly, so allow the same bounded -// build window as an ordinary complete snapshot before rejecting the session. pub const REQUIRED_CONFIG_BOOTSTRAP_BUILD_TIMEOUT: Duration = CONFIG_SNAPSHOT_BUILD_TIMEOUT; const MAX_ACTIVE_FANOUT_WORKERS: usize = 64; /// Concurrent snapshot builds allowed per pooled database connection. Builds @@ -579,9 +574,16 @@ async fn publish_sandbox_component_now(state: &Arc, key: &DeliveryK return Ok(None); }; match key.component { - ConfigComponentKind::SandboxConfig => build_sandbox_config_snapshot(state, &sandbox) - .await - .map(|snapshot| SupervisorConfigMessage::SandboxConfig(Box::new(snapshot))), + ConfigComponentKind::SandboxConfig => { + let snapshot = build_sandbox_config_snapshot(state, &sandbox).await?; + crate::config_update_operation::associate_pending_with_snapshot( + state, + &key.sandbox_id, + &snapshot, + ) + .await?; + Ok(SupervisorConfigMessage::SandboxConfig(Box::new(snapshot))) + } ConfigComponentKind::ProviderEnvironment => { build_provider_environment_snapshot(state, &sandbox, true) .await @@ -1302,85 +1304,4 @@ mod tests { .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 state = test_server_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(); - 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::PREVIOUS_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_update_operation.rs b/crates/openshell-server/src/config_update_operation.rs new file mode 100644 index 0000000000..e402c7b73e --- /dev/null +++ b/crates/openshell-server/src/config_update_operation.rs @@ -0,0 +1,1031 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Durable completion tracking for sandbox-scoped desired-state updates. + +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use futures::{StreamExt as _, stream}; +use metrics::{counter, gauge, histogram}; +use openshell_core::ObjectId; +use openshell_core::proto::{ + ConfigApplyOutcome, ConfigComponent, ConfigComponentApplyResult, ConfigSnapshotRevision, + ConfigUpdateOperation, ConfigUpdateOperationState, ObjectMeta, Sandbox, SandboxConfigRevision, + SandboxPhase, UpdateConfigResponse, config_snapshot_revision, +}; +use tonic::Status; +use tracing::{debug, info, warn}; +use uuid::Uuid; + +use crate::ServerState; +use crate::persistence::{KnownVersionUpdate, ObjectType, current_time_ms}; +use crate::storage_proto::StoredConfigUpdateOperation; + +pub const CONFIG_UPDATE_OPERATION_OBJECT_TYPE: &str = "config_update_operation"; +const OPERATION_SCAN_PAGE_SIZE: u32 = 250; +const MAX_TRANSITION_RETRIES: usize = 8; +const DEFAULT_WAIT_TIMEOUT: Duration = Duration::from_mins(1); +const MAX_WAIT_TIMEOUT: Duration = Duration::from_hours(1); +const MAX_SANITIZED_ERROR_BYTES: usize = 1_024; + +/// Gateway-local wakeups for callers waiting on one durable operation. +#[derive(Debug, Clone)] +pub struct OperationWatchBus { + inner: Arc>>>, +} + +impl OperationWatchBus { + #[must_use] + pub fn new() -> Self { + Self { + inner: Arc::new(Mutex::new(HashMap::new())), + } + } + + fn sender_for(&self, operation_id: &str) -> tokio::sync::broadcast::Sender<()> { + let mut inner = self + .inner + .lock() + .expect("operation watch bus lock poisoned"); + inner + .entry(operation_id.to_string()) + .or_insert_with(|| tokio::sync::broadcast::channel(16).0) + .clone() + } + + pub fn subscribe(&self, operation_id: &str) -> tokio::sync::broadcast::Receiver<()> { + self.sender_for(operation_id).subscribe() + } + + pub fn notify(&self, operation_id: &str) { + let sender = self + .inner + .lock() + .expect("operation watch bus lock poisoned") + .remove(operation_id); + if let Some(sender) = sender { + let _ = sender.send(()); + } + } +} + +impl ObjectType for StoredConfigUpdateOperation { + fn object_type() -> &'static str { + CONFIG_UPDATE_OPERATION_OBJECT_TYPE + } +} + +#[derive(Debug, Clone, Copy)] +pub struct OperationTarget { + pub policy_version: u32, + pub settings_revision: u64, +} + +#[derive(Debug, Clone, Default)] +pub struct CommittedResponse { + pub policy_version: u32, + pub policy_hash: String, + pub settings_revision: u64, + pub deleted: bool, + pub annotations: HashMap, +} + +pub fn operation_name(sandbox_id: &str, idempotency_key: &str, operation_id: &str) -> String { + if idempotency_key.is_empty() { + operation_id.to_string() + } else { + format!("{sandbox_id}:{idempotency_key}") + } +} + +fn initial_state(phase: SandboxPhase) -> ConfigUpdateOperationState { + match phase { + SandboxPhase::Stopped | SandboxPhase::Completed => ConfigUpdateOperationState::Inactive, + SandboxPhase::Deleting => ConfigUpdateOperationState::Cancelled, + SandboxPhase::Unspecified + | SandboxPhase::Provisioning + | SandboxPhase::Ready + | SandboxPhase::Error + | SandboxPhase::Unknown + | SandboxPhase::Stopping + | SandboxPhase::Starting => ConfigUpdateOperationState::Pending, + } +} + +pub fn sandbox_phase(sandbox: &Sandbox) -> SandboxPhase { + sandbox + .status + .as_ref() + .and_then(|status| SandboxPhase::try_from(status.phase).ok()) + .unwrap_or_default() +} + +pub fn new_record( + sandbox: &Sandbox, + workspace: &str, + idempotency_key: &str, + target: OperationTarget, + response: CommittedResponse, +) -> StoredConfigUpdateOperation { + let operation_id = Uuid::new_v4().to_string(); + let now = current_time_ms(); + let phase = sandbox_phase(sandbox); + let state = initial_state(phase); + let completed_at_ms = if terminal(state) { now } else { 0 }; + StoredConfigUpdateOperation { + metadata: Some(ObjectMeta { + id: operation_id.clone(), + name: operation_name(sandbox.object_id(), idempotency_key, &operation_id), + created_at_ms: now, + workspace: workspace.to_string(), + ..Default::default() + }), + operation: Some(ConfigUpdateOperation { + operation_id, + sandbox_id: sandbox.object_id().to_string(), + component: ConfigComponent::SandboxConfig.into(), + target_revision: None, + state: state.into(), + outcome: ConfigApplyOutcome::Unspecified.into(), + sanitized_error: String::new(), + created_at_ms: now, + updated_at_ms: now, + completed_at_ms, + }), + target_policy_version: target.policy_version, + target_settings_revision: target.settings_revision, + initial_phase: phase.into(), + idempotency_key: idempotency_key.to_string(), + attempt_count: 0, + next_attempt_at_ms: now, + response_policy_version: response.policy_version, + response_policy_hash: response.policy_hash, + response_settings_revision: response.settings_revision, + response_deleted: response.deleted, + response_annotations: response.annotations, + } +} + +pub async fn find_idempotent( + state: &ServerState, + workspace: &str, + sandbox_id: &str, + idempotency_key: &str, +) -> Result, Status> { + if idempotency_key.is_empty() { + return Ok(None); + } + state + .store + .get_message_by_name::( + workspace, + &operation_name(sandbox_id, idempotency_key, ""), + ) + .await + .map_err(|error| Status::internal(format!("fetch update operation failed: {error}"))) +} + +pub async fn get_record( + state: &ServerState, + operation_id: &str, +) -> Result, Status> { + state + .store + .get_message::(operation_id) + .await + .map_err(|error| Status::internal(format!("fetch update operation failed: {error}"))) +} + +/// Rebuild operation query columns from protobuf payloads before selective +/// reconciliation starts. Re-running after an interrupted startup is safe. +pub async fn repair_query_projections(state: &ServerState) -> Result<(), Status> { + let mut offset = 0; + let mut repaired = 0_u64; + loop { + let records = state + .store + .list_all_messages::(OPERATION_SCAN_PAGE_SIZE, offset) + .await + .map_err(|error| { + Status::internal(format!("list update operations for repair failed: {error}")) + })?; + let page_len = records.len(); + for mut record in records { + for _ in 0..MAX_TRANSITION_RETRIES { + let Some(metadata) = record.metadata.as_ref() else { + return Err(Status::internal("update operation metadata missing")); + }; + let operation_id = metadata.id.clone(); + let resource_version = metadata.resource_version; + if state + .store + .repair_config_operation_projection(&record, resource_version) + .await + .map_err(|error| { + Status::internal(format!( + "repair update operation projection failed: {error}" + )) + })? + { + repaired = repaired.saturating_add(1); + break; + } + let Some(current) = get_record(state, &operation_id).await? else { + break; + }; + record = current; + } + } + if page_len < OPERATION_SCAN_PAGE_SIZE as usize { + break; + } + offset = offset.saturating_add(OPERATION_SCAN_PAGE_SIZE); + } + if repaired > 0 { + info!( + repaired, + "configuration update operation projection repair complete" + ); + } + Ok(()) +} + +pub fn public_operation( + record: &StoredConfigUpdateOperation, +) -> Result { + record + .operation + .clone() + .ok_or_else(|| Status::internal("stored update operation payload missing")) +} + +pub fn response_from_record( + record: &StoredConfigUpdateOperation, +) -> Result { + Ok(UpdateConfigResponse { + version: record.response_policy_version, + policy_hash: record.response_policy_hash.clone(), + settings_revision: record.response_settings_revision, + deleted: record.response_deleted, + annotations: record.response_annotations.clone(), + operation: Some(public_operation(record)?), + }) +} + +pub fn terminal(state: ConfigUpdateOperationState) -> bool { + matches!( + state, + ConfigUpdateOperationState::Applied + | ConfigUpdateOperationState::Inactive + | ConfigUpdateOperationState::Failed + | ConfigUpdateOperationState::Superseded + | ConfigUpdateOperationState::Cancelled + ) +} + +fn sanitize_error(value: &str) -> String { + let mut end = value.len().min(MAX_SANITIZED_ERROR_BYTES); + while !value.is_char_boundary(end) { + end = end.saturating_sub(1); + } + value[..end].to_string() +} + +async fn mutate_record( + state: &ServerState, + operation_id: &str, + mut mutate: F, +) -> Result, Status> +where + F: FnMut(&mut StoredConfigUpdateOperation) -> bool, +{ + for _ in 0..MAX_TRANSITION_RETRIES { + let Some(current) = get_record(state, operation_id).await? else { + return Ok(None); + }; + let version = current + .metadata + .as_ref() + .map_or(0, |metadata| metadata.resource_version); + let mut candidate = current.clone(); + let changed = mutate(&mut candidate); + if !changed { + return Ok(Some((current, false))); + } + let updated = state + .store + .update_config_operation_cas(&candidate, version) + .await; + match updated { + Ok(KnownVersionUpdate::Changed(updated)) => return Ok(Some((updated, true))), + Ok(KnownVersionUpdate::Conflict) => {} + Err(error) => { + return Err(Status::internal(format!( + "persist update operation transition failed: {error}" + ))); + } + } + } + Err(Status::aborted( + "update operation changed concurrently; retry the operation query", + )) +} + +async fn finish( + state: &ServerState, + operation_id: &str, + terminal_state: ConfigUpdateOperationState, + outcome: ConfigApplyOutcome, + error: &str, +) -> Result<(), Status> { + let now = current_time_ms(); + let transition = mutate_record(state, operation_id, |record| { + let Some(operation) = record.operation.as_mut() else { + return false; + }; + if ConfigUpdateOperationState::try_from(operation.state) + != Ok(ConfigUpdateOperationState::Pending) + { + return false; + } + operation.state = terminal_state.into(); + operation.outcome = outcome.into(); + operation.sanitized_error = sanitize_error(error); + operation.updated_at_ms = now; + operation.completed_at_ms = now; + true + }) + .await?; + record_terminal_transition(state, transition, terminal_state); + Ok(()) +} + +async fn finish_if_target_matches( + state: &ServerState, + operation_id: &str, + requested_revision: &ConfigSnapshotRevision, + terminal_state: ConfigUpdateOperationState, + outcome: ConfigApplyOutcome, + error: &str, +) -> Result<(), Status> { + let now = current_time_ms(); + let transition = mutate_record(state, operation_id, |record| { + let Some(operation) = record.operation.as_mut() else { + return false; + }; + if ConfigUpdateOperationState::try_from(operation.state) + != Ok(ConfigUpdateOperationState::Pending) + || operation.target_revision.as_ref() != Some(requested_revision) + { + return false; + } + operation.state = terminal_state.into(); + operation.outcome = outcome.into(); + operation.sanitized_error = sanitize_error(error); + operation.updated_at_ms = now; + operation.completed_at_ms = now; + true + }) + .await?; + record_terminal_transition(state, transition, terminal_state); + Ok(()) +} + +fn record_terminal_transition( + state: &ServerState, + transition: Option<(StoredConfigUpdateOperation, bool)>, + terminal_state: ConfigUpdateOperationState, +) { + if let Some((record, true)) = transition { + counter!( + "openshell_config_update_operations_terminal_total", + "state" => terminal_state.as_str_name() + ) + .increment(1); + if let Some(operation) = record.operation.as_ref() { + state + .config_update_operation_watch_bus + .notify(&operation.operation_id); + state.sandbox_watch_bus.notify(&operation.sandbox_id); + } + } +} + +fn snapshot_revision( + snapshot: &openshell_core::proto::SandboxConfigSnapshot, +) -> ConfigSnapshotRevision { + ConfigSnapshotRevision { + component: Some(config_snapshot_revision::Component::SandboxConfig( + SandboxConfigRevision { + config_revision: snapshot.config_revision, + policy_version: snapshot.version, + policy_source: snapshot.policy_source, + global_policy_version: snapshot.global_policy_version, + settings_revision: snapshot.settings_revision, + }, + )), + } +} + +fn target_relation( + record: &StoredConfigUpdateOperation, + snapshot: &openshell_core::proto::SandboxConfigSnapshot, +) -> std::cmp::Ordering { + use std::cmp::Ordering; + + let policy = snapshot.version.cmp(&record.target_policy_version); + let settings = snapshot + .settings_revision + .cmp(&record.target_settings_revision); + if policy == Ordering::Equal && settings == Ordering::Equal { + Ordering::Equal + } else if policy == Ordering::Greater || settings == Ordering::Greater { + Ordering::Greater + } else { + Ordering::Less + } +} + +pub async fn reconcile_one(state: &Arc, operation_id: &str) -> Result<(), Status> { + let Some(record) = get_record(state, operation_id).await? else { + return Ok(()); + }; + reconcile_records_for_sandbox(state, vec![record]).await +} + +async fn reconcile_records_for_sandbox( + state: &Arc, + records: Vec, +) -> Result<(), Status> { + let Some(first_operation) = records.first().and_then(|record| record.operation.as_ref()) else { + return Ok(()); + }; + let sandbox_id = first_operation.sandbox_id.clone(); + + let Some(sandbox) = state + .store + .get_message::(&sandbox_id) + .await + .map_err(|error| Status::internal(format!("fetch operation sandbox failed: {error}")))? + else { + for record in records { + if let Some(operation) = record.operation.as_ref() { + finish( + state, + &operation.operation_id, + ConfigUpdateOperationState::Cancelled, + ConfigApplyOutcome::Unspecified, + "sandbox no longer exists", + ) + .await?; + } + } + return Ok(()); + }; + + match initial_state(sandbox_phase(&sandbox)) { + ConfigUpdateOperationState::Inactive => { + for record in records { + if let Some(operation) = record.operation.as_ref() { + finish( + state, + &operation.operation_id, + ConfigUpdateOperationState::Inactive, + ConfigApplyOutcome::Unspecified, + "", + ) + .await?; + } + } + return Ok(()); + } + ConfigUpdateOperationState::Cancelled => { + for record in records { + if let Some(operation) = record.operation.as_ref() { + finish( + state, + &operation.operation_id, + ConfigUpdateOperationState::Cancelled, + ConfigApplyOutcome::Unspecified, + "sandbox is deleting", + ) + .await?; + } + } + return Ok(()); + } + _ => {} + } + + let now = current_time_ms(); + let mut claimed_records = Vec::new(); + for record in records { + let operation = public_operation(&record)?; + let operation_state = + ConfigUpdateOperationState::try_from(operation.state).unwrap_or_default(); + if terminal(operation_state) { + continue; + } + let claimed = mutate_record(state, &operation.operation_id, |stored| { + let Some(operation) = stored.operation.as_mut() else { + return false; + }; + if ConfigUpdateOperationState::try_from(operation.state) + != Ok(ConfigUpdateOperationState::Pending) + || stored.next_attempt_at_ms > now + { + return false; + } + operation.updated_at_ms = now; + stored.attempt_count = stored.attempt_count.saturating_add(1); + let exponent = stored.attempt_count.min(8); + let delay_ms = 250_i64.saturating_mul(1_i64 << exponent).min(30_000); + stored.next_attempt_at_ms = now.saturating_add(delay_ms); + true + }) + .await?; + if let Some((claimed, true)) = claimed { + claimed_records.push(claimed); + } + } + if claimed_records.is_empty() { + return Ok(()); + } + + // Claims commit before admission to the bounded delivery queue. The queue + // builds the current snapshot once, records its exact revision on matching + // operations, then sends those same bytes. A failed admission remains + // recoverable when the claim's retry deadline expires. + let publish_provider_environment = claimed_records + .iter() + .any(|record| record.response_policy_version != 0); + let components = if publish_provider_environment { + crate::config_delivery::ConfigComponents::SANDBOX_AND_PROVIDER + } else { + crate::config_delivery::ConfigComponents::SANDBOX_CONFIG + }; + crate::config_delivery::publish_sandbox_components(state, &sandbox_id, components); + Ok(()) +} + +/// Associate pending operations with the exact snapshot that the delivery +/// worker is about to send. The caller must skip delivery if this fails. +pub async fn associate_pending_with_snapshot( + state: &Arc, + sandbox_id: &str, + snapshot: &openshell_core::proto::SandboxConfigSnapshot, +) -> Result<(), Status> { + let records = state + .store + .list_pending_config_operations_for_scope(sandbox_id) + .await + .map_err(|error| Status::internal(format!("list update operations failed: {error}")))?; + let target_revision = snapshot_revision(snapshot); + let now = current_time_ms(); + for record in records { + let operation = public_operation(&record)?; + match target_relation(&record, snapshot) { + std::cmp::Ordering::Greater => { + finish( + state, + &operation.operation_id, + ConfigUpdateOperationState::Superseded, + ConfigApplyOutcome::IgnoredStale, + "a newer desired revision replaced this update before application", + ) + .await?; + } + std::cmp::Ordering::Less => { + debug!( + operation_id = operation.operation_id, + "desired revision has not reached update operation target" + ); + } + std::cmp::Ordering::Equal => { + let _ = mutate_record(state, &operation.operation_id, |stored| { + let Some(operation) = stored.operation.as_mut() else { + return false; + }; + if ConfigUpdateOperationState::try_from(operation.state) + != Ok(ConfigUpdateOperationState::Pending) + || operation.target_revision.as_ref() == Some(&target_revision) + { + return false; + } + operation.target_revision = Some(target_revision); + operation.updated_at_ms = now; + true + }) + .await?; + } + } + } + Ok(()) +} + +pub async fn complete_from_apply_results( + state: &Arc, + sandbox_id: &str, + results: &[ConfigComponentApplyResult], +) -> Result<(), Status> { + let relevant: Vec<_> = results + .iter() + .filter(|result| result.component != ConfigComponent::ProviderEnvironment as i32) + .collect(); + if relevant.is_empty() { + return Ok(()); + } + let operations = state + .store + .list_pending_config_operations_for_scope(sandbox_id) + .await + .map_err(|error| Status::internal(format!("list update operations failed: {error}")))?; + for record in operations { + let Some(operation) = record.operation.as_ref() else { + continue; + }; + for result in &relevant { + let requested = result + .requested_revision + .as_ref() + .ok_or_else(|| Status::invalid_argument("configuration result revision missing"))?; + if operation.component != result.component + || operation.target_revision.as_ref() != Some(requested) + { + continue; + } + let outcome = ConfigApplyOutcome::try_from(result.outcome).unwrap_or_default(); + let terminal_state = match outcome { + ConfigApplyOutcome::Applied + | ConfigApplyOutcome::IgnoredDuplicate + | ConfigApplyOutcome::Degraded => ConfigUpdateOperationState::Applied, + ConfigApplyOutcome::IgnoredStale => ConfigUpdateOperationState::Superseded, + ConfigApplyOutcome::RetainedLocalOverride + | ConfigApplyOutcome::FailedRetainedLastKnownGood + | ConfigApplyOutcome::FailedClosed + | ConfigApplyOutcome::Unsupported + | ConfigApplyOutcome::Unspecified => ConfigUpdateOperationState::Failed, + }; + let failure = result + .failure + .as_ref() + .map_or("", |failure| failure.message.as_str()); + finish_if_target_matches( + state, + &operation.operation_id, + requested, + terminal_state, + outcome, + failure, + ) + .await?; + break; + } + } + Ok(()) +} + +pub async fn complete_from_apply_result( + state: &Arc, + sandbox_id: &str, + result: &ConfigComponentApplyResult, +) -> Result<(), Status> { + complete_from_apply_results(state, sandbox_id, std::slice::from_ref(result)).await +} + +pub async fn wait_for_terminal( + state: &Arc, + operation_id: &str, + timeout_secs: u32, +) -> Result { + let timeout = if timeout_secs == 0 { + DEFAULT_WAIT_TIMEOUT + } else { + Duration::from_secs(u64::from(timeout_secs)).min(MAX_WAIT_TIMEOUT) + }; + let started = std::time::Instant::now(); + let deadline = tokio::time::Instant::now() + timeout; + // Subscribe first, then perform the authoritative read. A wakeup is only a + // hint; the fallback poll covers other replicas and process restarts. + let mut wake = state + .config_update_operation_watch_bus + .subscribe(operation_id); + loop { + let record = get_record(state, operation_id) + .await? + .ok_or_else(|| Status::not_found("update operation not found"))?; + let operation = public_operation(&record)?; + let operation_state = + ConfigUpdateOperationState::try_from(operation.state).unwrap_or_default(); + if terminal(operation_state) { + histogram!("openshell_config_update_operation_wait_seconds") + .record(started.elapsed().as_secs_f64()); + return Ok(operation); + } + if tokio::time::Instant::now() >= deadline { + let mut status = Status::deadline_exceeded(format!( + "timed out waiting for update operation {operation_id}" + )); + if let Ok(value) = operation_id.parse() { + status.metadata_mut().insert("operation-id", value); + } + return Err(status); + } + tokio::select! { + () = tokio::time::sleep_until((tokio::time::Instant::now() + Duration::from_secs(1)).min(deadline)) => {} + _ = wake.recv() => {} + } + } +} + +async fn reconcile_sandbox(state: &Arc, sandbox_id: &str) -> Result<(), Status> { + let operations = state + .store + .list_pending_config_operations_for_scope(sandbox_id) + .await + .map_err(|error| Status::internal(format!("list sandbox operations failed: {error}")))?; + reconcile_records_for_sandbox(state, operations).await +} + +async fn reconcile_due_batch(state: &Arc) { + let now = current_time_ms(); + match state + .store + .list_due_config_update_operations(now, OPERATION_SCAN_PAGE_SIZE) + .await + { + Ok(operations) => { + let mut sandbox_groups = std::collections::BTreeMap::<_, Vec<_>>::new(); + for record in operations { + let Some(sandbox_id) = record + .operation + .as_ref() + .map(|operation| operation.sandbox_id.clone()) + else { + continue; + }; + sandbox_groups.entry(sandbox_id).or_default().push(record); + } + let concurrency = state.store.max_connections().saturating_sub(1).max(1) as usize; + stream::iter(sandbox_groups) + .for_each_concurrent(concurrency, |(sandbox_id, records)| { + let state = state.clone(); + async move { + if let Err(error) = reconcile_records_for_sandbox(&state, records).await { + warn!(sandbox_id, error = %error, "update operation reconciliation failed"); + } + } + }) + .await; + } + Err(error) => warn!(error = %error, "failed to list due update operations"), + } + match state.store.count_pending_config_update_operations().await { + Ok(pending) => { + gauge!("openshell_config_update_operations_pending") + .set(u32::try_from(pending).unwrap_or(u32::MAX)); + } + Err(error) => warn!(error = %error, "failed to count pending update operations"), + } +} + +pub fn spawn_reconciler(state: Arc, interval: Duration) { + let mut changed_sandboxes = state.sandbox_watch_bus.subscribe_all(); + tokio::spawn(async move { + let mut timer = tokio::time::interval(interval); + timer.tick().await; + loop { + tokio::select! { + _ = timer.tick() => reconcile_due_batch(&state).await, + changed = changed_sandboxes.recv() => { + match changed { + Ok(sandbox_id) => { + if let Err(error) = reconcile_sandbox(&state, &sandbox_id).await { + warn!(sandbox_id, error = %error, "sandbox update operation reconciliation failed"); + } + } + Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => { + reconcile_due_batch(&state).await; + } + Err(tokio::sync::broadcast::error::RecvError::Closed) => return, + } + } + } + } + }); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::grpc::test_support::test_server_state; + use openshell_core::proto::{SandboxConfigSnapshot, SandboxSpec}; + + async fn pending_test_operation() -> (Arc, StoredConfigUpdateOperation) { + let state = test_server_state().await; + let sandbox = Sandbox { + metadata: Some(ObjectMeta { + id: "operation-test-sandbox".to_string(), + name: "operation-test-sandbox".to_string(), + workspace: "default".to_string(), + ..Default::default() + }), + spec: Some(SandboxSpec::default()), + ..Default::default() + }; + state.store.put_message(&sandbox).await.unwrap(); + let record = new_record( + &sandbox, + "default", + "operation-test-request", + OperationTarget { + policy_version: 0, + settings_revision: 1, + }, + CommittedResponse::default(), + ); + state + .store + .put_if_with_operation( + crate::grpc::policy::SANDBOX_SETTINGS_OBJECT_TYPE, + "operation-test-settings", + "operation-test-sandbox", + "default", + br#"{"revision":1,"settings":{}}"#, + crate::persistence::WriteCondition::MustCreate, + &record, + None, + ) + .await + .unwrap(); + let operation_id = &record.operation.as_ref().unwrap().operation_id; + ( + state.clone(), + get_record(&state, operation_id).await.unwrap().unwrap(), + ) + } + + #[test] + fn authoritative_phase_classification_is_explicit() { + assert_eq!( + initial_state(SandboxPhase::Ready), + ConfigUpdateOperationState::Pending + ); + assert_eq!( + initial_state(SandboxPhase::Provisioning), + ConfigUpdateOperationState::Pending + ); + assert_eq!( + initial_state(SandboxPhase::Starting), + ConfigUpdateOperationState::Pending + ); + assert_eq!( + initial_state(SandboxPhase::Stopping), + ConfigUpdateOperationState::Pending + ); + assert_eq!( + initial_state(SandboxPhase::Error), + ConfigUpdateOperationState::Pending + ); + assert_eq!( + initial_state(SandboxPhase::Stopped), + ConfigUpdateOperationState::Inactive + ); + assert_eq!( + initial_state(SandboxPhase::Completed), + ConfigUpdateOperationState::Inactive + ); + assert_eq!( + initial_state(SandboxPhase::Deleting), + ConfigUpdateOperationState::Cancelled + ); + } + + #[test] + fn target_relation_requires_exact_policy_and_settings_tuple() { + let record = StoredConfigUpdateOperation { + target_policy_version: 7, + target_settings_revision: 11, + ..Default::default() + }; + let snapshot = |version, settings_revision| SandboxConfigSnapshot { + version, + settings_revision, + ..Default::default() + }; + + assert_eq!( + target_relation(&record, &snapshot(7, 11)), + std::cmp::Ordering::Equal + ); + assert_eq!( + target_relation(&record, &snapshot(8, 11)), + std::cmp::Ordering::Greater + ); + assert_eq!( + target_relation(&record, &snapshot(7, 12)), + std::cmp::Ordering::Greater + ); + assert_eq!( + target_relation(&record, &snapshot(6, 11)), + std::cmp::Ordering::Less + ); + } + + #[test] + fn sanitized_errors_are_bounded_on_utf8_boundaries() { + let value = "é".repeat(MAX_SANITIZED_ERROR_BYTES); + let sanitized = sanitize_error(&value); + assert!(sanitized.len() <= MAX_SANITIZED_ERROR_BYTES); + assert!(sanitized.is_char_boundary(sanitized.len())); + } + + #[tokio::test] + async fn concurrent_claims_commit_once_and_noop_does_not_churn_version() { + let (state, record) = pending_test_operation().await; + let operation_id = record.operation.as_ref().unwrap().operation_id.clone(); + let now = current_time_ms(); + let claim = || async { + mutate_record(&state, &operation_id, |stored| { + if stored.next_attempt_at_ms > now { + return false; + } + stored.attempt_count = stored.attempt_count.saturating_add(1); + stored.next_attempt_at_ms = now.saturating_add(1_000); + stored.operation.as_mut().unwrap().updated_at_ms = now; + true + }) + .await + .unwrap() + }; + let (first, second) = tokio::join!(claim(), claim()); + let committed = usize::from(first.as_ref().is_some_and(|(_, changed)| *changed)) + + usize::from(second.as_ref().is_some_and(|(_, changed)| *changed)); + assert_eq!(committed, 1); + + let claimed = get_record(&state, &operation_id).await.unwrap().unwrap(); + assert_eq!(claimed.attempt_count, 1); + let version = claimed.metadata.as_ref().unwrap().resource_version; + let unchanged = mutate_record(&state, &operation_id, |_| false) + .await + .unwrap() + .unwrap(); + assert!(!unchanged.1); + assert_eq!( + get_record(&state, &operation_id) + .await + .unwrap() + .unwrap() + .metadata + .unwrap() + .resource_version, + version + ); + } + + #[tokio::test] + async fn completion_rechecks_exact_target_inside_cas_transition() { + let (state, record) = pending_test_operation().await; + let operation_id = record.operation.as_ref().unwrap().operation_id.clone(); + let expected = ConfigSnapshotRevision { + component: Some(config_snapshot_revision::Component::SandboxConfig( + SandboxConfigRevision { + config_revision: 1, + policy_version: 1, + settings_revision: 1, + ..Default::default() + }, + )), + }; + mutate_record(&state, &operation_id, |stored| { + stored.operation.as_mut().unwrap().target_revision = Some(expected); + true + }) + .await + .unwrap(); + let before = get_record(&state, &operation_id).await.unwrap().unwrap(); + + finish_if_target_matches( + &state, + &operation_id, + &ConfigSnapshotRevision::default(), + ConfigUpdateOperationState::Applied, + ConfigApplyOutcome::Applied, + "", + ) + .await + .unwrap(); + + let after = get_record(&state, &operation_id).await.unwrap().unwrap(); + assert_eq!( + after.metadata.as_ref().unwrap().resource_version, + before.metadata.as_ref().unwrap().resource_version + ); + assert_eq!( + ConfigUpdateOperationState::try_from(after.operation.unwrap().state).unwrap(), + ConfigUpdateOperationState::Pending + ); + } +} diff --git a/crates/openshell-server/src/credentials.rs b/crates/openshell-server/src/credentials.rs index 922a0b8ebe..98fc79e25d 100644 --- a/crates/openshell-server/src/credentials.rs +++ b/crates/openshell-server/src/credentials.rs @@ -93,6 +93,7 @@ pub trait CredentialDriver: std::fmt::Debug + Send + Sync { fn fail_next_delete(&self) {} #[cfg(test)] + #[allow(dead_code)] fn gate_next_resolve( &self, ) -> Option<( @@ -279,6 +280,7 @@ impl CredentialRuntime { } #[cfg(test)] + #[allow(dead_code)] pub(crate) fn gate_next_resolve( &self, ) -> ( diff --git a/crates/openshell-server/src/grpc/mod.rs b/crates/openshell-server/src/grpc/mod.rs index 15bbcd13b0..242b769e99 100644 --- a/crates/openshell-server/src/grpc/mod.rs +++ b/crates/openshell-server/src/grpc/mod.rs @@ -28,14 +28,14 @@ use openshell_core::proto::{ DetachSandboxProviderResponse, EditDraftChunkRequest, EditDraftChunkResponse, ExchangeProviderSubjectTokenRequest, ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, ExposeServiceRequest, FinalizeMainProcessExitRequest, - FinalizeMainProcessExitResponse, GatewayMessage, GetCurrentUserRequest, GetCurrentUserResponse, + FinalizeMainProcessExitResponse, GatewayMessage, GetConfigUpdateOperationRequest, + GetConfigUpdateOperationResponse, GetCurrentUserRequest, GetCurrentUserResponse, GetDraftHistoryRequest, GetDraftHistoryResponse, GetDraftPolicyRequest, GetDraftPolicyResponse, GetGatewayConfigRequest, GetGatewayConfigResponse, GetGatewayInfoRequest, GetGatewayInfoResponse, GetProviderProfileRequest, GetProviderRefreshStatusRequest, GetProviderRefreshStatusResponse, GetProviderRequest, GetSandboxConfigRequest, GetSandboxConfigResponse, GetSandboxLogsRequest, GetSandboxLogsResponse, - GetSandboxPolicyStatusRequest, GetSandboxPolicyStatusResponse, - GetSandboxProviderEnvironmentRequest, GetSandboxProviderEnvironmentResponse, GetSandboxRequest, + GetSandboxPolicyStatusRequest, GetSandboxPolicyStatusResponse, GetSandboxRequest, GetSandboxTemplateRequest, GetServiceRequest, GetWorkspaceRequest, GetWorkspaceResponse, GpuResourceCapabilities, HealthRequest, HealthResponse, ImportProviderProfilesRequest, ImportProviderProfilesResponse, IssueSandboxTokenRequest, IssueSandboxTokenResponse, @@ -550,13 +550,6 @@ impl OpenShell for OpenShellService { policy::handle_get_gateway_config(&self.state, request).await } - async fn get_sandbox_provider_environment( - &self, - request: Request, - ) -> Result, Status> { - policy::handle_get_sandbox_provider_environment(&self.state, request).await - } - async fn exchange_provider_subject_token( &self, request: Request, @@ -571,6 +564,13 @@ impl OpenShell for OpenShellService { policy::handle_update_config(&self.state, request).await } + async fn get_config_update_operation( + &self, + request: Request, + ) -> Result, Status> { + policy::handle_get_config_update_operation(&self.state, request).await + } + async fn get_sandbox_policy_status( &self, request: Request, diff --git a/crates/openshell-server/src/grpc/policy.rs b/crates/openshell-server/src/grpc/policy.rs index 4a309655b3..f60878bcf6 100644 --- a/crates/openshell-server/src/grpc/policy.rs +++ b/crates/openshell-server/src/grpc/policy.rs @@ -16,10 +16,12 @@ use crate::auth::workspace_authz::{ MinWorkspaceRole, authorize_sandbox_workspace, authorize_workspace_selector, require_platform_admin, selected_workspace_name, }; +use crate::config_update_operation::{self, CommittedResponse, OperationTarget}; use crate::pagination::Pagination; +#[cfg(test)] +use crate::persistence::ObjectType; use crate::persistence::{ - DraftChunkRecord, ObjectId, ObjectListQuery, ObjectName, ObjectType, ObjectWorkspace, - PolicyRecord, Store, + DraftChunkRecord, ObjectId, ObjectListQuery, ObjectName, ObjectWorkspace, PolicyRecord, Store, }; use crate::policy_store::{AtomicPolicyRevisionWrite, PolicyStoreExt}; use crate::provider_profile_sources::EffectiveProviderProfileCatalog; @@ -38,19 +40,23 @@ use openshell_core::proto::{ AddAllowRules as ProtoAddAllowRules, AddDenyRules as ProtoAddDenyRules, ApproveAllDraftChunksRequest, ApproveAllDraftChunksResponse, ApproveDraftChunkRequest, ApproveDraftChunkResponse, ClearDraftChunksRequest, ClearDraftChunksResponse, - DraftHistoryEntry, EditDraftChunkRequest, EditDraftChunkResponse, EffectiveSetting, + ConfigUpdateConsistency, DraftHistoryEntry, EditDraftChunkRequest, EditDraftChunkResponse, + EffectiveSetting, GetConfigUpdateOperationRequest, GetConfigUpdateOperationResponse, GetDraftHistoryRequest, GetDraftHistoryResponse, GetDraftPolicyRequest, GetDraftPolicyResponse, GetGatewayConfigRequest, GetGatewayConfigResponse, GetSandboxConfigRequest, GetSandboxConfigResponse, GetSandboxLogsRequest, GetSandboxLogsResponse, - GetSandboxPolicyStatusRequest, GetSandboxPolicyStatusResponse, + GetSandboxPolicyStatusRequest, GetSandboxPolicyStatusResponse, ListSandboxPoliciesRequest, + ListSandboxPoliciesResponse, PolicyChunk, PolicyMergeOperation, PolicySource, PolicyStatus, + ProviderEnvironmentSnapshot, ProviderEnvironmentValue, ProviderEnvironmentValueClassification, + PushSandboxLogsRequest, PushSandboxLogsResponse, RejectDraftChunkRequest, + RejectDraftChunkResponse, ReportPolicyStatusRequest, ReportPolicyStatusResponse, + SandboxConfigSnapshot, SandboxLogLine, SandboxPolicyRevision, SettingScope, SettingValue, + SubmitPolicyAnalysisRequest, SubmitPolicyAnalysisResponse, UndoDraftChunkRequest, + UndoDraftChunkResponse, UpdateConfigRequest, UpdateConfigResponse, +}; +#[cfg(test)] +use openshell_core::proto::{ GetSandboxProviderEnvironmentRequest, GetSandboxProviderEnvironmentResponse, - ListSandboxPoliciesRequest, ListSandboxPoliciesResponse, PolicyChunk, PolicyMergeOperation, - PolicySource, PolicyStatus, ProviderEnvironmentSnapshot, ProviderEnvironmentValue, - ProviderEnvironmentValueClassification, PushSandboxLogsRequest, PushSandboxLogsResponse, - RejectDraftChunkRequest, RejectDraftChunkResponse, ReportPolicyStatusRequest, - ReportPolicyStatusResponse, SandboxConfigSnapshot, SandboxLogLine, SandboxPolicyRevision, - SettingScope, SettingValue, SubmitPolicyAnalysisRequest, SubmitPolicyAnalysisResponse, - UndoDraftChunkRequest, UndoDraftChunkResponse, UpdateConfigRequest, UpdateConfigResponse, }; use openshell_core::proto::{ L7DenyRule, L7Rule, NetworkBinary, NetworkEndpoint, NetworkPolicyRule, Provider, Sandbox, @@ -117,6 +123,25 @@ const STORED_POLICY_SOURCE_SPEC: &str = "sandbox spec policy"; const STORED_POLICY_SOURCE_GLOBAL: &str = "global policy setting"; /// Maximum number of optimistic retry attempts for policy version conflicts. const MERGE_RETRY_LIMIT: usize = 5; +const MAX_IDEMPOTENCY_KEY_BYTES: usize = 256; + +async fn finish_config_update_operation( + state: &Arc, + operation_id: &str, + consistency: ConfigUpdateConsistency, + timeout_secs: u32, +) -> Result, Status> { + config_update_operation::reconcile_one(state, operation_id).await?; + if consistency == ConfigUpdateConsistency::WaitForApply { + config_update_operation::wait_for_terminal(state, operation_id, timeout_secs).await?; + } + let record = config_update_operation::get_record(state, operation_id) + .await? + .ok_or_else(|| Status::internal("committed update operation disappeared"))?; + Ok(Response::new( + config_update_operation::response_from_record(&record)?, + )) +} // Private wire-only compatibility types for policy history written before // 0.1.0. Public generated bindings intentionally reserve NetworkBinary tag 2, @@ -1733,19 +1758,41 @@ async fn current_effective_policy_for_sandbox( .map(|spec| spec.providers.clone()) .unwrap_or_default(); let global_settings = load_global_settings(state.store.as_ref()).await?; - if let Some(global_policy) = decode_policy_from_global_settings(&global_settings)? { + let provider_records = super::provider::load_provider_environment_records( + state.store.as_ref(), + workspace, + &provider_names, + ) + .await?; + current_effective_policy_for_sandbox_from_inputs( + state, + catalog, + &global_settings, + &provider_records, + sandbox, + sandbox_id, + ) + .await +} + +async fn current_effective_policy_for_sandbox_from_inputs( + state: &ServerState, + catalog: &EffectiveProviderProfileCatalog, + global_settings: &StoredSettings, + provider_records: &[super::provider::ProviderEnvironmentRecord], + sandbox: &Sandbox, + sandbox_id: &str, +) -> Result { + if let Some(global_policy) = decode_policy_from_global_settings(global_settings)? { // A global policy is the complete effective policy. Dormant sandbox // history and specs may predate the current schema, but they must not // prevent the valid global policy from being served. - return apply_effective_policy_context( - state, + return apply_effective_policy_context_from_records( catalog, - workspace, - &provider_names, + provider_records, global_policy, PolicySource::Global, - ) - .await; + ); } let policy = if let Some(record) = state @@ -1764,15 +1811,12 @@ async fn current_effective_policy_for_sandbox( } }; - apply_effective_policy_context( - state, + apply_effective_policy_context_from_records( catalog, - workspace, - &provider_names, + provider_records, policy, PolicySource::Sandbox, ) - .await } async fn effective_policy_for_source( @@ -1807,17 +1851,26 @@ async fn apply_effective_policy_context( catalog: &EffectiveProviderProfileCatalog, workspace: &str, provider_names: &[String], - mut policy: ProtoSandboxPolicy, + policy: ProtoSandboxPolicy, policy_source: PolicySource, ) -> Result { - clear_provider_credentialed_markers(&mut policy); - let mut provider_context = provider_policy_context_with_catalog( + let provider_records = super::provider::load_provider_environment_records( state.store.as_ref(), - catalog, workspace, provider_names, ) .await?; + apply_effective_policy_context_from_records(catalog, &provider_records, policy, policy_source) +} + +fn apply_effective_policy_context_from_records( + catalog: &EffectiveProviderProfileCatalog, + provider_records: &[super::provider::ProviderEnvironmentRecord], + mut policy: ProtoSandboxPolicy, + policy_source: PolicySource, +) -> Result { + clear_provider_credentialed_markers(&mut policy); + let mut provider_context = provider_policy_context_from_records(catalog, provider_records); if !matches!(policy_source, PolicySource::Global) && !provider_context.layers.is_empty() { policy = compose_effective_policy(&policy, &provider_context.layers); } @@ -2128,6 +2181,23 @@ async fn validate_policy_credential_bindings_for_sandbox( Ok(bindings) } +fn validate_policy_credential_bindings_from_records( + catalog: &EffectiveProviderProfileCatalog, + records: &[super::provider::ProviderEnvironmentRecord], + policy: &ProtoSandboxPolicy, +) -> Result<(), Status> { + let bindings = policy_static_credential_endpoint_bindings(Some(policy))?; + let has_signing = policy.network_policies.values().any(|rule| { + rule.endpoints + .iter() + .any(|endpoint| !endpoint.credential_signing.is_empty()) + }); + if bindings.is_empty() && !has_signing { + return Ok(()); + } + validate_policy_credential_binding_context(catalog, records, policy, &bindings) +} + async fn provider_policy_layers_for_sandbox( state: &ServerState, workspace: &str, @@ -2378,6 +2448,7 @@ fn update_config_response( settings_revision, deleted, annotations, + operation: None, }) } @@ -2521,6 +2592,12 @@ pub async fn build_sandbox_config_snapshot( .provider_profile_sources .snapshot_catalog(state.store.as_ref(), &workspace) .await?; + let provider_records = super::provider::load_provider_environment_records( + state.store.as_ref(), + &workspace, + &sandbox_provider_names, + ) + .await?; let global_settings = load_global_settings(state.store.as_ref()).await?; let global_policy = decode_policy_from_global_settings(&global_settings)?; @@ -2579,16 +2656,10 @@ pub async fn build_sandbox_config_snapshot( } }; - let global_settings = load_global_settings(state.store.as_ref()).await?; let sandbox_settings = load_sandbox_settings(state.store.as_ref(), &workspace, sandbox.object_name()).await?; - let mut provider_policy_context = provider_policy_context_with_catalog( - state.store.as_ref(), - &provider_profile_catalog, - &workspace, - &sandbox_provider_names, - ) - .await?; + let mut provider_policy_context = + provider_policy_context_from_records(&provider_profile_catalog, &provider_records); if matches!(policy_source, PolicySource::Global) && let Ok(Some(global_rev)) = state @@ -2666,23 +2737,17 @@ pub async fn build_sandbox_config_snapshot( state.sandbox_jwt_issuer.is_some(), ); if let Some(policy) = policy.as_ref() { - validate_policy_credential_bindings_for_sandbox( - state.as_ref(), + validate_policy_credential_bindings_from_records( &provider_profile_catalog, - &workspace, - &sandbox_provider_names, + &provider_records, policy, - ) - .await?; + )?; } - let provider_env_revision = compute_provider_env_revision_with_catalog_and_policy_bindings( - state.store.as_ref(), + let provider_env_revision = compute_provider_env_revision_from_records_and_policy_bindings( &provider_profile_catalog, - &workspace, - &sandbox_provider_names, + &provider_records, &policy_credential_bindings, - ) - .await?; + )?; Ok(SandboxConfigSnapshot { policy, @@ -2701,6 +2766,7 @@ pub async fn build_sandbox_config_snapshot( .as_str() .to_string(), extension_authentication_enabled: state.sandbox_jwt_issuer.is_some(), + settings_revision: sandbox_settings.revision, }) } @@ -2718,6 +2784,7 @@ fn sandbox_config_response(snapshot: SandboxConfigSnapshot) -> GetSandboxConfigR workspace: snapshot.workspace, policy_validation_failure_mode: snapshot.policy_validation_failure_mode, extension_authentication_enabled: snapshot.extension_authentication_enabled, + settings_revision: snapshot.settings_revision, } } @@ -2863,6 +2930,7 @@ pub(super) async fn compute_provider_env_revision_with_catalog( .await } +#[cfg(test)] async fn compute_provider_env_revision_with_catalog_and_policy_bindings( store: &Store, catalog: &EffectiveProviderProfileCatalog, @@ -3090,16 +3158,23 @@ async fn provider_policy_context_with_catalog( workspace: &str, provider_names: &[String], ) -> Result { + let records = + super::provider::load_provider_environment_records(store, workspace, provider_names) + .await?; + Ok(provider_policy_context_from_records(catalog, &records)) +} + +fn provider_policy_context_from_records( + catalog: &EffectiveProviderProfileCatalog, + records: &[super::provider::ProviderEnvironmentRecord], +) -> ProviderPolicyContext { let mut layers = Vec::new(); let mut credentialed_scopes = Vec::new(); let mut endpointless_provider_names = HashSet::new(); - for name in provider_names { - let provider = store - .get_message_by_name::(workspace, name) - .await - .map_err(|e| Status::internal(format!("failed to fetch provider '{name}': {e}")))? - .ok_or_else(|| Status::failed_precondition(format!("provider '{name}' not found")))?; + for record in records { + let name = &record.name; + let provider = &record.provider; let provider_type = provider.r#type.trim(); let Some(profile) = super::provider::get_provider_type_profile_for_scope( @@ -3115,12 +3190,12 @@ async fn provider_policy_context_with_catalog( continue; }; - if !super::provider::provider_profile_endpoints_are_active(&profile, &provider) { + if !super::provider::provider_profile_endpoints_are_active(&profile, provider) { endpointless_provider_names.insert(name.clone()); continue; } - let rule_name = openshell_policy::provider_rule_name(provider.object_name()); + let rule_name = openshell_policy::provider_rule_name(name); let mut rule = profile.network_policy_rule(&rule_name); if rule.endpoints.is_empty() { endpointless_provider_names.insert(name.clone()); @@ -3143,11 +3218,11 @@ async fn provider_policy_context_with_catalog( }); } - Ok(ProviderPolicyContext { + ProviderPolicyContext { layers, credentialed_scopes, endpointless_provider_names, - }) + } } fn endpoint_ports(endpoint: &NetworkEndpoint) -> Vec { @@ -3319,6 +3394,7 @@ pub(super) async fn handle_get_gateway_config( })) } +#[cfg(test)] pub(super) async fn handle_get_sandbox_provider_environment( state: &Arc, request: Request, @@ -3365,10 +3441,12 @@ pub async fn build_provider_environment_snapshot( &provider_names, ) .await?; - let effective_policy = current_effective_policy_for_sandbox( + let global_settings = load_global_settings(state.store.as_ref()).await?; + let effective_policy = current_effective_policy_for_sandbox_from_inputs( state.as_ref(), &provider_profile_catalog, - &workspace, + &global_settings, + &provider_records, sandbox, &sandbox_id, ) @@ -3475,6 +3553,7 @@ pub async fn build_provider_environment_snapshot( }) } +#[cfg(test)] fn provider_environment_response( snapshot: ProviderEnvironmentSnapshot, ) -> GetSandboxProviderEnvironmentResponse { @@ -3544,6 +3623,23 @@ async fn handle_update_config_inner( sandbox_caller: bool, ) -> Result, Status> { let req = request.into_inner(); + let consistency = ConfigUpdateConsistency::try_from(req.consistency) + .map_err(|_| Status::invalid_argument("unknown update consistency"))?; + let consistency = if consistency == ConfigUpdateConsistency::Unspecified { + ConfigUpdateConsistency::CommitOnly + } else { + consistency + }; + if req.idempotency_key.len() > MAX_IDEMPOTENCY_KEY_BYTES { + return Err(Status::invalid_argument(format!( + "idempotency_key exceeds {MAX_IDEMPOTENCY_KEY_BYTES} bytes" + ))); + } + if !req.idempotency_key.is_empty() && req.idempotency_key.trim() != req.idempotency_key { + return Err(Status::invalid_argument( + "idempotency_key must not have leading or trailing whitespace", + )); + } validate_annotations(&req.annotations, "annotations")?; let workspace = if req.global { if req.workspace_scope.is_some() { @@ -3602,6 +3698,16 @@ async fn handle_update_config_inner( )); } if req.global { + if consistency == ConfigUpdateConsistency::WaitForApply { + return Err(Status::invalid_argument( + "WAIT_FOR_APPLY is only supported for sandbox-scoped updates", + )); + } + if !req.idempotency_key.is_empty() { + return Err(Status::invalid_argument( + "idempotency_key is only supported for sandbox-scoped updates", + )); + } if !req.annotations.is_empty() { return Err(Status::invalid_argument( "annotations are only supported for sandbox-scoped updates", @@ -3812,9 +3918,42 @@ async fn handle_update_config_inner( let sandbox_id = sandbox.object_id().to_string(); let mut response_annotations = sandbox_metadata_annotations(&sandbox); - if has_setting { - let _settings_guard = state.settings_mutex.lock().await; + if let Some(existing) = config_update_operation::find_idempotent( + state, + &workspace, + &sandbox_id, + &req.idempotency_key, + ) + .await? + { + let operation = config_update_operation::public_operation(&existing)?; + return finish_config_update_operation( + state, + &operation.operation_id, + consistency, + req.wait_timeout_secs, + ) + .await; + } + + // Avoid redundant validation and snapshot construction within one gateway. + // The database transaction owns cross-replica serialization and completes + // the unchanged target dimension after locking the sandbox fence. + let config_guard = state.settings_mutex.lock().await; + + let mut projected_annotations = response_annotations.clone(); + projected_annotations.extend(req.annotations.clone()); + let sandbox_projection_payload = if projected_annotations == response_annotations { + None + } else { + let mut projected_sandbox = sandbox.clone(); + if let Some(metadata) = projected_sandbox.metadata.as_mut() { + metadata.annotations.clone_from(&projected_annotations); + } + Some(projected_sandbox.encode_to_vec()) + }; + if has_setting { if key == POLICY_SETTING_KEY { return Err(Status::invalid_argument( "reserved key 'policy' must be set via policy commands", @@ -3835,22 +3974,55 @@ async fn handle_update_config_inner( load_sandbox_settings(state.store.as_ref(), &workspace, sandbox.object_name()) .await?; let removed = sandbox_settings.settings.remove(key).is_some(); + let mut operation_id = None; if removed { sandbox_settings.revision = sandbox_settings.revision.wrapping_add(1); - save_sandbox_settings( + let operation_record = config_update_operation::new_record( + &sandbox, + &workspace, + &req.idempotency_key, + OperationTarget { + policy_version: 0, + settings_revision: sandbox_settings.revision, + }, + CommittedResponse { + settings_revision: sandbox_settings.revision, + deleted: true, + annotations: projected_annotations.clone(), + ..Default::default() + }, + ); + operation_id = Some( + config_update_operation::public_operation(&operation_record)?.operation_id, + ); + save_sandbox_settings_with_operation( state.store.as_ref(), &workspace, sandbox.object_name(), &sandbox_settings, + &operation_record, + sandbox_projection_payload + .as_deref() + .map(|payload| crate::persistence::AtomicSandboxProjection { + sandbox_id: &sandbox_id, + payload, + expected_resource_version: req.expected_resource_version, + }) + .as_ref(), ) .await?; - crate::config_delivery::publish_sandbox_components( - state, - &sandbox_id, - crate::config_delivery::ConfigComponents::SANDBOX_CONFIG, - ); } + drop(config_guard); + if let Some(operation_id) = operation_id { + return finish_config_update_operation( + state, + &operation_id, + consistency, + req.wait_timeout_secs, + ) + .await; + } response_annotations = persist_update_config_annotations( state, &sandbox_id, @@ -3859,7 +4031,6 @@ async fn handle_update_config_inner( &response_annotations, ) .await?; - return Ok(update_config_response( 0, String::new(), @@ -3884,22 +4055,53 @@ async fn handle_update_config_inner( let mut sandbox_settings = load_sandbox_settings(state.store.as_ref(), &workspace, sandbox.object_name()).await?; let changed = upsert_setting_value(&mut sandbox_settings.settings, key, stored); + let mut operation_id = None; if changed { sandbox_settings.revision = sandbox_settings.revision.wrapping_add(1); - save_sandbox_settings( + let operation_record = config_update_operation::new_record( + &sandbox, + &workspace, + &req.idempotency_key, + OperationTarget { + policy_version: 0, + settings_revision: sandbox_settings.revision, + }, + CommittedResponse { + settings_revision: sandbox_settings.revision, + annotations: projected_annotations.clone(), + ..Default::default() + }, + ); + operation_id = + Some(config_update_operation::public_operation(&operation_record)?.operation_id); + save_sandbox_settings_with_operation( state.store.as_ref(), &workspace, sandbox.object_name(), &sandbox_settings, + &operation_record, + sandbox_projection_payload + .as_deref() + .map(|payload| crate::persistence::AtomicSandboxProjection { + sandbox_id: &sandbox_id, + payload, + expected_resource_version: req.expected_resource_version, + }) + .as_ref(), ) .await?; - crate::config_delivery::publish_sandbox_components( - state, - &sandbox_id, - crate::config_delivery::ConfigComponents::SANDBOX_CONFIG, - ); } + drop(config_guard); + if let Some(operation_id) = operation_id { + return finish_config_update_operation( + state, + &operation_id, + consistency, + req.wait_timeout_secs, + ) + .await; + } response_annotations = persist_update_config_annotations( state, &sandbox_id, @@ -3908,7 +4110,6 @@ async fn handle_update_config_inner( &response_annotations, ) .await?; - return Ok(update_config_response( 0, String::new(), @@ -3940,9 +4141,11 @@ async fn handle_update_config_inner( expected_resource_version: req.expected_resource_version, provenance: &req.annotations, annotations: &req.annotations, + sandbox: &sandbox, + idempotency_key: &req.idempotency_key, }; let baseline_policy = spec.policy.clone(); - let (version, hash, updated_sandbox) = apply_merge_operations_with_retry( + let (version, hash, updated_sandbox, operation_id) = apply_merge_operations_with_retry( state.store.as_ref(), &sandbox_id, &workspace, @@ -3956,11 +4159,7 @@ async fn handle_update_config_inner( Some(&atomic_context), ) .await?; - crate::config_delivery::publish_sandbox_components( - state, - &sandbox_id, - crate::config_delivery::ConfigComponents::SANDBOX_AND_PROVIDER, - ); + drop(config_guard); response_annotations = if let Some(updated_sandbox) = updated_sandbox { sandbox_metadata_annotations(&updated_sandbox) } else { @@ -4009,6 +4208,15 @@ async fn handle_update_config_inner( ); emit_config_update_policy_success(sandbox_caller); + if let Some(operation_id) = operation_id { + return finish_config_update_operation( + state, + &operation_id, + consistency, + req.wait_timeout_secs, + ) + .await; + } return Ok(update_config_response( u32::try_from(version).unwrap_or(0), hash, @@ -4099,7 +4307,7 @@ async fn handle_update_config_inner( let payload = new_policy.encode_to_vec(); let hash = deterministic_policy_hash(&new_policy); - let (_next_version, committed_annotations) = { + let (next_version, _committed_annotations, operation_id) = { let mut committed = None; for attempt in 1..=MERGE_RETRY_LIMIT { let latest = state @@ -4137,6 +4345,25 @@ async fn handle_update_config_inner( } let next_version = latest.as_ref().map_or(1, |record| record.version + 1); + let mut operation_annotations = response_annotations.clone(); + operation_annotations.extend(req.annotations.clone()); + let operation_record = config_update_operation::new_record( + &sandbox, + &workspace, + &req.idempotency_key, + OperationTarget { + policy_version: u32::try_from(next_version).unwrap_or(u32::MAX), + settings_revision: 0, + }, + CommittedResponse { + policy_version: u32::try_from(next_version).unwrap_or(u32::MAX), + policy_hash: hash.clone(), + annotations: operation_annotations, + ..Default::default() + }, + ); + let operation_id = + config_update_operation::public_operation(&operation_record)?.operation_id; let write = AtomicPolicyRevisionWrite { id: uuid::Uuid::new_v4().to_string(), sandbox_id: sandbox_id.clone(), @@ -4148,12 +4375,16 @@ async fn handle_update_config_inner( expected_resource_version: req.expected_resource_version, annotations: req.annotations.clone(), backfill_policy: backfill_policy.clone(), + operation: Some(operation_record), }; match state.store.put_policy_revision_atomic(&write).await { Ok(updated_sandbox) => { - committed = - Some((next_version, sandbox_metadata_annotations(&updated_sandbox))); + committed = Some(( + next_version, + sandbox_metadata_annotations(&updated_sandbox), + operation_id, + )); break; } Err(error) if error.is_unique_violation_on("objects_version_uq") => { @@ -4179,12 +4410,7 @@ async fn handle_update_config_inner( )) })? }; - response_annotations = committed_annotations; - crate::config_delivery::publish_sandbox_components( - state, - &sandbox_id, - crate::config_delivery::ConfigComponents::SANDBOX_AND_PROVIDER, - ); + drop(config_guard); state.sandbox_watch_bus.notify(&sandbox_id); if backfill_policy.is_some() { @@ -4194,56 +4420,6 @@ async fn handle_update_config_inner( ); } - let latest = state - .store - .get_latest_policy(&sandbox_id) - .await - .map_err(|e| Status::internal(format!("fetch latest policy failed: {e}")))?; - - let payload = new_policy.encode_to_vec(); - let hash = deterministic_policy_hash(&new_policy); - - if let Some(ref current) = latest - && canonical_policy_record_matches_for_deduplication(current, &hash) - { - return Ok(Response::new(UpdateConfigResponse { - version: u32::try_from(current.version).unwrap_or(0), - policy_hash: hash, - settings_revision: 0, - deleted: false, - annotations: response_annotations, - })); - } - - let next_version = latest.map_or(1, |r| r.version + 1); - let policy_id = uuid::Uuid::new_v4().to_string(); - - state - .store - .put_policy_revision( - &policy_id, - &sandbox_id, - &workspace, - next_version, - &payload, - &hash, - ) - .await - .map_err(|e| Status::internal(format!("persist policy revision failed: {e}")))?; - - crate::config_delivery::publish_sandbox_components( - state, - &sandbox_id, - crate::config_delivery::ConfigComponents::SANDBOX_AND_PROVIDER, - ); - - let _ = state - .store - .supersede_older_policies(&sandbox_id, next_version) - .await; - - state.sandbox_watch_bus.notify(&sandbox_id); - info!( sandbox_id = %sandbox_id, version = next_version, @@ -4252,13 +4428,7 @@ async fn handle_update_config_inner( ); emit_full_policy_update_success(sandbox_caller, next_version); - Ok(update_config_response( - u32::try_from(next_version).unwrap_or(0), - hash, - 0, - false, - response_annotations, - )) + finish_config_update_operation(state, &operation_id, consistency, req.wait_timeout_secs).await } // --------------------------------------------------------------------------- @@ -4338,6 +4508,40 @@ pub(super) async fn handle_get_sandbox_policy_status( })) } +pub(super) async fn handle_get_config_update_operation( + state: &Arc, + request: Request, +) -> Result, Status> { + let principal = super::extract_principal(&request)?; + let req = request.into_inner(); + if req.operation_id.is_empty() { + return Err(Status::invalid_argument("operation_id is required")); + } + let authz = authorize_workspace_selector( + &state.store, + &state.admin_role, + &principal, + req.workspace_scope.as_ref(), + MinWorkspaceRole::User, + ) + .await?; + let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) + .await? + .name; + let record = config_update_operation::get_record(state, &req.operation_id) + .await? + .filter(|record| { + record + .metadata + .as_ref() + .is_some_and(|metadata| metadata.workspace == workspace) + }) + .ok_or_else(|| Status::not_found("update operation not found"))?; + Ok(Response::new(GetConfigUpdateOperationResponse { + operation: Some(config_update_operation::public_operation(&record)?), + })) +} + pub(super) async fn handle_list_sandbox_policies( state: &Arc, request: Request, @@ -5672,7 +5876,7 @@ async fn handle_approve_all_draft_chunks_inner( ) .await { - Ok((version, hash, _)) => (version, hash), + Ok((version, hash, _, _)) => (version, hash), Err(status) => { for (chunk, _, _) in &accepted { persist_pending_application_error(state, &chunk.id, &status).await; @@ -6831,6 +7035,8 @@ struct AtomicPolicyWriteContext<'a> { expected_resource_version: u64, provenance: &'a HashMap, annotations: &'a HashMap, + sandbox: &'a Sandbox, + idempotency_key: &'a str, } struct PolicyCredentialBindingValidationContext<'a> { @@ -6985,7 +7191,7 @@ async fn apply_merge_operations_with_retry( validation_context: PolicyMergeValidationContext<'_>, expected_current_effective_hash: Option<&str>, atomic_context: Option<&AtomicPolicyWriteContext<'_>>, -) -> Result<(i64, String, Option), Status> { +) -> Result<(i64, String, Option, Option), Status> { let provider_layers = validation_context.provider_layers; for attempt in 1..=MERGE_RETRY_LIMIT { let latest = store @@ -7044,11 +7250,11 @@ async fn apply_merge_operations_with_retry( && current_hash.as_deref() == Some(hash.as_str()) && atomic_context.is_none_or(|context| current.provenance == *context.provenance) { - return Ok((current.version, hash, None)); + return Ok((current.version, hash, None, None)); } if latest.is_none() && !merged.changed { - return Ok((0, hash, None)); + return Ok((0, hash, None, None)); } let payload = new_policy.encode_to_vec(); @@ -7056,6 +7262,25 @@ async fn apply_merge_operations_with_retry( let policy_id = uuid::Uuid::new_v4().to_string(); let write_result = if let Some(context) = atomic_context { + let mut response_annotations = sandbox_metadata_annotations(context.sandbox); + response_annotations.extend(context.annotations.clone()); + let operation_record = config_update_operation::new_record( + context.sandbox, + workspace, + context.idempotency_key, + OperationTarget { + policy_version: u32::try_from(next_version).unwrap_or(u32::MAX), + settings_revision: 0, + }, + CommittedResponse { + policy_version: u32::try_from(next_version).unwrap_or(u32::MAX), + policy_hash: hash.clone(), + annotations: response_annotations, + ..Default::default() + }, + ); + let operation_id = + config_update_operation::public_operation(&operation_record)?.operation_id; store .put_policy_revision_atomic(&AtomicPolicyRevisionWrite { id: policy_id, @@ -7068,9 +7293,10 @@ async fn apply_merge_operations_with_retry( expected_resource_version: context.expected_resource_version, annotations: context.annotations.clone(), backfill_policy: None, + operation: Some(operation_record), }) .await - .map(Some) + .map(|sandbox| (Some(sandbox), Some(operation_id))) } else { store .put_policy_revision( @@ -7082,11 +7308,11 @@ async fn apply_merge_operations_with_retry( &hash, ) .await - .map(|()| None) + .map(|()| (None, None)) }; match write_result { - Ok(updated_sandbox) => { + Ok((updated_sandbox, operation_id)) => { if atomic_context.is_none() { let _ = store .supersede_older_policies(sandbox_id, next_version) @@ -7103,7 +7329,7 @@ async fn apply_merge_operations_with_retry( ); } - return Ok((next_version, hash, updated_sandbox)); + return Ok((next_version, hash, updated_sandbox, operation_id)); } Err(e) => { if e.is_unique_violation_on("objects_version_uq") { @@ -7165,7 +7391,7 @@ async fn merge_chunk_into_policy_with_validation( None, ) .await - .map(|(version, hash, _)| (version, hash)) + .map(|(version, hash, _, _)| (version, hash)) } #[cfg(test)] @@ -7212,7 +7438,7 @@ async fn remove_chunk_from_policy( None, ) .await - .map(|(version, hash, _)| (version, hash)) + .map(|(version, hash, _, _)| (version, hash)) } // --------------------------------------------------------------------------- @@ -7333,6 +7559,7 @@ pub(super) async fn load_sandbox_settings( load_settings_record(store, SANDBOX_SETTINGS_OBJECT_TYPE, workspace, sandbox_name).await } +#[cfg(test)] pub(super) async fn save_sandbox_settings( store: &Store, workspace: &str, @@ -7349,6 +7576,55 @@ pub(super) async fn save_sandbox_settings( .await } +async fn save_sandbox_settings_with_operation( + store: &Store, + workspace: &str, + sandbox_name: &str, + settings: &StoredSettings, + operation: &crate::storage_proto::StoredConfigUpdateOperation, + sandbox_projection: Option<&crate::persistence::AtomicSandboxProjection<'_>>, +) -> Result<(), Status> { + use crate::persistence::WriteCondition; + + let payload = serde_json::to_vec(settings) + .map_err(|error| Status::internal(format!("encode settings payload failed: {error}")))?; + let (id, condition) = if settings.resource_version == 0 { + (uuid::Uuid::new_v4().to_string(), WriteCondition::MustCreate) + } else { + let existing = store + .get_by_name(SANDBOX_SETTINGS_OBJECT_TYPE, workspace, sandbox_name) + .await + .map_err(|error| Status::internal(format!("fetch settings for CAS failed: {error}")))? + .ok_or_else(|| Status::not_found("settings disappeared since load"))?; + ( + existing.id, + WriteCondition::MatchResourceVersion(settings.resource_version), + ) + }; + store + .put_if_with_operation( + SANDBOX_SETTINGS_OBJECT_TYPE, + &id, + sandbox_name, + workspace, + &payload, + condition, + operation, + sandbox_projection, + ) + .await + .map_err(|error| match error { + crate::persistence::PersistenceError::Conflict { .. } => { + Status::aborted("settings were modified concurrently; please retry") + } + crate::persistence::PersistenceError::UniqueViolation { .. } => Status::aborted( + "settings or idempotency key was created concurrently; please retry", + ), + other => super::persistence_error_to_status(other, "persist settings and operation"), + })?; + Ok(()) +} + async fn load_settings_record( store: &Store, object_type: &str, @@ -7527,6 +7803,7 @@ mod tests { }; use crate::grpc::test_support::{authed_request, test_server_state}; use crate::persistence::test_store; + use openshell_core::proto::{ConfigApplyOutcome, ConfigComponent, ConfigComponentApplyResult}; use std::collections::HashMap; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -8684,7 +8961,7 @@ mod tests { .await .expect("store legacy merge base"); - let (version, hash, _) = apply_merge_operations_with_retry( + let (version, hash, _, _) = apply_merge_operations_with_retry( &store, &sandbox_id, "default", @@ -11430,7 +11707,6 @@ mod tests { Vec::new(), ); state.store.put_message(&sandbox).await.unwrap(); - let error = super::super::sandbox::handle_attach_sandbox_provider( &state, authed_request(openshell_core::proto::AttachSandboxProviderRequest { @@ -20030,6 +20306,9 @@ mod tests { workspace_scope: Some(openshell_core::proto::workspace_selector( "default".to_string(), )), + consistency: ConfigUpdateConsistency::CommitOnly.into(), + idempotency_key: String::new(), + wait_timeout_secs: 0, }), ) .await @@ -20128,6 +20407,9 @@ mod tests { workspace_scope: Some(openshell_core::proto::workspace_selector( "default".to_string(), )), + consistency: ConfigUpdateConsistency::CommitOnly.into(), + idempotency_key: String::new(), + wait_timeout_secs: 0, }), ) .await @@ -21160,6 +21442,9 @@ mod tests { workspace_scope: Some(openshell_core::proto::workspace_selector( "default".to_string(), )), + consistency: ConfigUpdateConsistency::CommitOnly.into(), + idempotency_key: String::new(), + wait_timeout_secs: 0, }), ) .await @@ -21261,6 +21546,9 @@ mod tests { workspace_scope: Some(openshell_core::proto::workspace_selector( "default".to_string(), )), + consistency: ConfigUpdateConsistency::CommitOnly.into(), + idempotency_key: String::new(), + wait_timeout_secs: 0, }), ) .await @@ -21603,8 +21891,245 @@ mod tests { ); } + #[tokio::test] + async fn stopped_sandbox_setting_update_commits_with_inactive_operation() { + let state = test_server_state().await; + let mut sandbox = test_sandbox( + "sb-inactive-operation", + "inactive-operation", + ProtoSandboxPolicy::default(), + Vec::new(), + ); + sandbox.set_phase(openshell_core::proto::SandboxPhase::Stopped as i32); + state.store.put_message(&sandbox).await.unwrap(); + let sandbox = state + .store + .get_message_by_name::("default", "inactive-operation") + .await + .unwrap() + .unwrap(); + let expected_resource_version = sandbox.metadata.as_ref().unwrap().resource_version; + + let request = || { + with_user(Request::new(UpdateConfigRequest { + name: "inactive-operation".to_string(), + setting_key: "ocsf_json_enabled".to_string(), + setting_value: Some(SettingValue { + value: Some(setting_value::Value::BoolValue(true)), + }), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + consistency: ConfigUpdateConsistency::WaitForApply.into(), + idempotency_key: "inactive-setting-1".to_string(), + expected_resource_version, + annotations: HashMap::from([("change-ticket".to_string(), "1234".to_string())]), + ..Default::default() + })) + }; + + let first = handle_update_config(&state, request()) + .await + .unwrap() + .into_inner(); + let operation = first.operation.expect("durable operation"); + assert_eq!( + openshell_core::proto::ConfigUpdateOperationState::try_from(operation.state).unwrap(), + openshell_core::proto::ConfigUpdateOperationState::Inactive + ); + assert_eq!(first.settings_revision, 1); + + let retried = handle_update_config(&state, request()) + .await + .unwrap() + .into_inner(); + assert_eq!( + retried.operation.unwrap().operation_id, + operation.operation_id + ); + assert_eq!(retried.settings_revision, first.settings_revision); + + let stored = load_sandbox_settings(&state.store, "default", "inactive-operation") + .await + .unwrap(); + assert_eq!( + stored.settings.get("ocsf_json_enabled"), + Some(&StoredSettingValue::Bool(true)) + ); + let stored_sandbox = state + .store + .get_message_by_name::("default", "inactive-operation") + .await + .unwrap() + .unwrap(); + assert_eq!( + stored_sandbox + .metadata + .as_ref() + .unwrap() + .annotations + .get("change-ticket") + .map(String::as_str), + Some("1234") + ); + } + + #[tokio::test] + async fn terminal_operation_transition_wakes_local_waiter() { + let state = test_server_state().await; + let sandbox = test_sandbox( + "sb-operation-wake", + "operation-wake", + ProtoSandboxPolicy::default(), + Vec::new(), + ); + state.store.put_message(&sandbox).await.unwrap(); + state + .store + .put_policy_revision( + "operation-wake-policy", + sandbox.object_id(), + "default", + 1, + &ProtoSandboxPolicy::default().encode_to_vec(), + "operation-wake-policy-hash", + ) + .await + .unwrap(); + let response = handle_update_config( + &state, + with_user(Request::new(UpdateConfigRequest { + name: sandbox.object_name().to_string(), + setting_key: "ocsf_json_enabled".to_string(), + setting_value: Some(SettingValue { + value: Some(setting_value::Value::BoolValue(true)), + }), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + consistency: ConfigUpdateConsistency::CommitOnly.into(), + idempotency_key: "wake-local-waiter".to_string(), + ..Default::default() + })), + ) + .await + .unwrap() + .into_inner(); + let operation = response.operation.unwrap(); + let operation_id = operation.operation_id.clone(); + let snapshot = build_sandbox_config_snapshot(&state, &sandbox) + .await + .unwrap(); + config_update_operation::associate_pending_with_snapshot( + &state, + sandbox.object_id(), + &snapshot, + ) + .await + .unwrap(); + let requested_revision = config_update_operation::get_record(&state, &operation_id) + .await + .unwrap() + .unwrap() + .operation + .unwrap() + .target_revision + .unwrap(); + let waiter_state = state.clone(); + let waiter = tokio::spawn(async move { + config_update_operation::wait_for_terminal(&waiter_state, &operation_id, 5).await + }); + tokio::task::yield_now().await; + config_update_operation::complete_from_apply_result( + &state, + sandbox.object_id(), + &ConfigComponentApplyResult { + component: ConfigComponent::SandboxConfig.into(), + requested_revision: Some(requested_revision), + outcome: ConfigApplyOutcome::Applied.into(), + ..Default::default() + }, + ) + .await + .unwrap(); + let terminal = tokio::time::timeout(std::time::Duration::from_millis(500), waiter) + .await + .expect("terminal transition should notify the local waiter") + .unwrap() + .unwrap(); + assert_eq!( + openshell_core::proto::ConfigUpdateOperationState::try_from(terminal.state).unwrap(), + openshell_core::proto::ConfigUpdateOperationState::Applied + ); + } + + #[tokio::test] + async fn operation_waiter_recovers_when_notification_is_missed() { + let state = test_server_state().await; + let sandbox = test_sandbox( + "sb-operation-poll", + "operation-poll", + ProtoSandboxPolicy::default(), + Vec::new(), + ); + state.store.put_message(&sandbox).await.unwrap(); + state + .store + .put_policy_revision( + "operation-poll-policy", + sandbox.object_id(), + "default", + 1, + &ProtoSandboxPolicy::default().encode_to_vec(), + "operation-poll-policy-hash", + ) + .await + .unwrap(); + let response = handle_update_config( + &state, + with_user(Request::new(UpdateConfigRequest { + name: sandbox.object_name().to_string(), + setting_key: "ocsf_json_enabled".to_string(), + setting_value: Some(SettingValue { + value: Some(setting_value::Value::BoolValue(true)), + }), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + consistency: ConfigUpdateConsistency::CommitOnly.into(), + idempotency_key: "poll-missed-notification".to_string(), + ..Default::default() + })), + ) + .await + .unwrap() + .into_inner(); + let operation_id = response.operation.unwrap().operation_id; + let waiter_state = state.clone(); + let waiter_operation_id = operation_id.clone(); + let waiter = tokio::spawn(async move { + config_update_operation::wait_for_terminal(&waiter_state, &waiter_operation_id, 5).await + }); + tokio::task::yield_now().await; + let mut record = config_update_operation::get_record(&state, &operation_id) + .await + .unwrap() + .unwrap(); + record.operation.as_mut().unwrap().state = + openshell_core::proto::ConfigUpdateOperationState::Applied.into(); + let resource_version = record.metadata.as_ref().unwrap().resource_version; + state + .store + .update_config_operation_cas(&record, resource_version) + .await + .unwrap(); + let terminal = tokio::time::timeout(std::time::Duration::from_secs(2), waiter) + .await + .expect("point polling should recover a missed notification") + .unwrap() + .unwrap(); + assert_eq!( + openshell_core::proto::ConfigUpdateOperationState::try_from(terminal.state).unwrap(), + openshell_core::proto::ConfigUpdateOperationState::Applied + ); + } + #[test] - fn provider_stream_values_expand_to_legacy_polling_response() { + fn provider_stream_values_preserve_credential_metadata() { let response = provider_environment_response(ProviderEnvironmentSnapshot { provider_env_revision: 9, values: vec![ diff --git a/crates/openshell-server/src/lib.rs b/crates/openshell-server/src/lib.rs index d7adb1714b..362cb825f2 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -19,6 +19,7 @@ pub mod cli; mod compute; mod config_delivery; pub mod config_file; +mod config_update_operation; mod credentials; mod defaults; mod gateway_listener; @@ -275,6 +276,9 @@ pub struct ServerState { /// In-memory bus for sandbox update notifications. pub sandbox_watch_bus: SandboxWatchBus, + /// Gateway-local wakeups for durable configuration-operation waiters. + pub(crate) config_update_operation_watch_bus: config_update_operation::OperationWatchBus, + /// In-memory bus for server process logs. pub tracing_log_bus: TracingLogBus, @@ -429,6 +433,7 @@ impl ServerState { credentials, sandbox_index, sandbox_watch_bus, + config_update_operation_watch_bus: config_update_operation::OperationWatchBus::new(), tracing_log_bus, telemetry: telemetry::TelemetryState::new(), ssh_connections_by_token: Mutex::new(HashMap::new()), @@ -704,6 +709,9 @@ pub(crate) async fn run_server( grpc::policy::backfill_legacy_policy_history(&state) .await .map_err(|error| Error::execution(error.to_string()))?; + config_update_operation::repair_query_projections(&state) + .await + .map_err(|error| Error::execution(error.to_string()))?; // Reconcile local-driver running intent before watchers spawn so their // first snapshots observe the post-start backend state. Explicitly stopped @@ -732,6 +740,7 @@ pub(crate) async fn run_server( ssh_sessions::spawn_session_reaper(store.clone(), Duration::from_hours(1)); supervisor_session::spawn_relay_reaper(state.clone(), Duration::from_secs(30)); config_delivery::spawn_owner_reconciler(state.clone(), Duration::from_secs(30)); + config_update_operation::spawn_reconciler(state.clone(), Duration::from_secs(5)); provider_refresh::spawn_refresh_worker(state.clone(), Duration::from_mins(1)); // Create the multiplexed service diff --git a/crates/openshell-server/src/multiplex.rs b/crates/openshell-server/src/multiplex.rs index f03aa4d07f..b4719b94ec 100644 --- a/crates/openshell-server/src/multiplex.rs +++ b/crates/openshell-server/src/multiplex.rs @@ -1426,7 +1426,6 @@ mod tests { "/openshell.v1.OpenShell/GetSandboxConfig", "/openshell.v1.OpenShell/ReportPolicyStatus", "/openshell.v1.OpenShell/PushSandboxLogs", - "/openshell.v1.OpenShell/GetSandboxProviderEnvironment", "/openshell.v1.OpenShell/SubmitPolicyAnalysis", "/openshell.v1.OpenShell/RefreshSandboxToken", ]; @@ -2860,7 +2859,6 @@ mod tests { "/openshell.v1.OpenShell/ReportPolicyStatus", "/openshell.v1.OpenShell/PushSandboxLogs", "/openshell.v1.OpenShell/SubmitPolicyAnalysis", - "/openshell.v1.OpenShell/GetSandboxProviderEnvironment", "/openshell.v1.OpenShell/ConnectSupervisor", "/openshell.v1.OpenShell/RelayStream", "/openshell.v1.OpenShell/IssueSandboxToken", diff --git a/crates/openshell-server/src/persistence/mod.rs b/crates/openshell-server/src/persistence/mod.rs index bc0e290079..3708ac03a4 100644 --- a/crates/openshell-server/src/persistence/mod.rs +++ b/crates/openshell-server/src/persistence/mod.rs @@ -28,6 +28,21 @@ pub const CONFIG_COMPONENT_OBSERVATION_OBJECT_TYPE: &str = "config_component_obs pub type PersistenceResult = Result; +/// Optional sandbox projection committed with a settings mutation and its +/// durable operation. +pub struct AtomicSandboxProjection<'a> { + pub sandbox_id: &'a str, + pub payload: &'a [u8], + pub expected_resource_version: u64, +} + +/// Result of a compare-and-swap update that already has the current payload. +#[derive(Debug)] +pub enum KnownVersionUpdate { + Changed(T), + Conflict, +} + /// Maximum number of object ids sent in one set-based delete statement. /// /// Keep this well below `SQLite`'s bind-variable limit. Backends split larger @@ -366,6 +381,91 @@ impl Store { )) } + /// Write desired state and its durable update operation in one database + /// transaction. Used by sandbox-scoped settings mutations. + #[allow(clippy::too_many_arguments)] + pub async fn put_if_with_operation( + &self, + object_type: &str, + id: &str, + name: &str, + workspace: &str, + payload: &[u8], + condition: WriteCondition, + operation: &crate::storage_proto::StoredConfigUpdateOperation, + sandbox_projection: Option<&AtomicSandboxProjection<'_>>, + ) -> PersistenceResult { + store_dispatch_traced!(self.put_if_with_operation( + object_type, + id, + name, + workspace, + payload, + condition, + operation, + sandbox_projection + )) + } + + /// Update an operation payload and its query columns with one CAS write. + pub async fn update_config_operation_cas( + &self, + operation: &crate::storage_proto::StoredConfigUpdateOperation, + expected_resource_version: u64, + ) -> PersistenceResult> + { + let resource_version = store_dispatch!( + self.update_config_operation_cas(operation, expected_resource_version) + )?; + let Some(resource_version) = resource_version else { + return Ok(KnownVersionUpdate::Conflict); + }; + let mut updated = operation.clone(); + updated.set_resource_version(resource_version); + Ok(KnownVersionUpdate::Changed(updated)) + } + + /// Repair operation query columns from the authoritative protobuf payload + /// without changing the payload or resource version. + pub async fn repair_config_operation_projection( + &self, + operation: &crate::storage_proto::StoredConfigUpdateOperation, + expected_resource_version: u64, + ) -> PersistenceResult { + store_dispatch!( + self.repair_config_operation_projection(operation, expected_resource_version) + ) + } + + /// Return pending operations for one sandbox. Terminal history is excluded + /// by SQL before protobuf payloads are decoded. + pub async fn list_pending_config_operations_for_scope( + &self, + scope: &str, + ) -> PersistenceResult> { + store_dispatch!(self.list_pending_config_operations_for_scope(scope))? + .into_iter() + .map(decode_record) + .collect() + } + + /// Return a bounded, stable batch of pending operations whose retry time + /// has arrived. + pub async fn list_due_config_update_operations( + &self, + now_ms: i64, + limit: u32, + ) -> PersistenceResult> { + store_dispatch!(self.list_due_config_update_operations(now_ms, limit))? + .into_iter() + .map(decode_record) + .collect() + } + + pub async fn count_pending_config_update_operations(&self) -> PersistenceResult { + store_dispatch!(self.count_pending_config_update_operations()) + } + /// Delete an object by id with compare-and-swap support. /// /// # Arguments diff --git a/crates/openshell-server/src/persistence/postgres.rs b/crates/openshell-server/src/persistence/postgres.rs index 655c1565fb..002c9470ce 100644 --- a/crates/openshell-server/src/persistence/postgres.rs +++ b/crates/openshell-server/src/persistence/postgres.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use super::{ - DraftChunkRecord, ObjectCursor, ObjectListQuery, ObjectRecord, PersistenceError, - PersistenceResult, PolicyRecord, WriteCondition, WriteResult, current_time_ms, map_db_error, - map_migrate_error, + AtomicSandboxProjection, DraftChunkRecord, ObjectCursor, ObjectListQuery, ObjectRecord, + PersistenceError, PersistenceResult, PolicyRecord, WriteCondition, WriteResult, + current_time_ms, map_db_error, map_migrate_error, }; use crate::policy_store::{ AtomicPolicyRevisionWrite, draft_chunk_payload_from_record, draft_chunk_record_from_parts, @@ -29,6 +29,118 @@ pub(super) fn embedded_migration_sql(version: i64) -> Option<&'static str> { use super::{DELETE_MANY_BATCH_SIZE, DRAFT_CHUNK_OBJECT_TYPE, POLICY_OBJECT_TYPE}; +async fn insert_update_operation_postgres( + tx: &mut sqlx::Transaction<'_, Postgres>, + record: &crate::storage_proto::StoredConfigUpdateOperation, + now_ms: i64, +) -> PersistenceResult<()> { + let metadata = record + .metadata + .as_ref() + .ok_or_else(|| PersistenceError::Encode("update operation metadata missing".to_string()))?; + let operation = record + .operation + .as_ref() + .ok_or_else(|| PersistenceError::Encode("update operation payload missing".to_string()))?; + let state = openshell_core::proto::ConfigUpdateOperationState::try_from(operation.state) + .unwrap_or_default(); + sqlx::query( + r" +INSERT INTO objects ( + object_type, id, name, workspace, scope, version, status, payload, + created_at_ms, updated_at_ms, labels, resource_version, next_attempt_at_ms +) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $9, '{}'::jsonb, 1, $10) +", + ) + .bind(crate::config_update_operation::CONFIG_UPDATE_OPERATION_OBJECT_TYPE) + .bind(&metadata.id) + .bind(&metadata.name) + .bind(&metadata.workspace) + .bind(&operation.sandbox_id) + .bind(Option::::None) + .bind(state.as_str_name()) + .bind(record.encode_to_vec()) + .bind(now_ms) + .bind(record.next_attempt_at_ms) + .execute(&mut **tx) + .await + .map_err(|error| map_db_error(&error))?; + Ok(()) +} + +async fn lock_sandbox_config_fence( + tx: &mut sqlx::Transaction<'_, Postgres>, + sandbox_id: &str, +) -> PersistenceResult<()> { + sqlx::query( + "INSERT INTO sandbox_config_fences (sandbox_id) VALUES ($1) ON CONFLICT DO NOTHING", + ) + .bind(sandbox_id) + .execute(&mut **tx) + .await + .map_err(|error| map_db_error(&error))?; + sqlx::query("SELECT sandbox_id FROM sandbox_config_fences WHERE sandbox_id = $1 FOR UPDATE") + .bind(sandbox_id) + .fetch_one(&mut **tx) + .await + .map_err(|error| map_db_error(&error))?; + Ok(()) +} + +async fn operation_with_current_policy_target( + tx: &mut sqlx::Transaction<'_, Postgres>, + record: &crate::storage_proto::StoredConfigUpdateOperation, +) -> PersistenceResult { + let sandbox_id = record + .operation + .as_ref() + .ok_or_else(|| PersistenceError::Encode("update operation payload missing".to_string()))? + .sandbox_id + .as_str(); + let version: Option = sqlx::query_scalar( + "SELECT version FROM objects WHERE object_type = $1 AND scope = $2 ORDER BY version DESC LIMIT 1", + ) + .bind(POLICY_OBJECT_TYPE) + .bind(sandbox_id) + .fetch_optional(&mut **tx) + .await + .map_err(|error| map_db_error(&error))?; + let mut record = record.clone(); + record.target_policy_version = + version.map_or(0, |value| u32::try_from(value).unwrap_or(u32::MAX)); + Ok(record) +} + +async fn operation_with_current_settings_target( + tx: &mut sqlx::Transaction<'_, Postgres>, + record: &crate::storage_proto::StoredConfigUpdateOperation, + workspace: &str, + sandbox_name: &str, +) -> PersistenceResult { + let payload: Option> = sqlx::query_scalar( + "SELECT payload FROM objects WHERE object_type = $1 AND workspace = $2 AND name = $3", + ) + .bind(crate::grpc::policy::SANDBOX_SETTINGS_OBJECT_TYPE) + .bind(workspace) + .bind(sandbox_name) + .fetch_optional(&mut **tx) + .await + .map_err(|error| map_db_error(&error))?; + let revision = payload + .as_deref() + .map(serde_json::from_slice::) + .transpose() + .map_err(|error| { + PersistenceError::Decode(format!("decode settings payload failed: {error}")) + })? + .and_then(|value| value.get("revision").and_then(serde_json::Value::as_u64)) + .unwrap_or(0); + let mut record = record.clone(); + record.target_settings_revision = revision; + Ok(record) +} + #[derive(Debug, Clone)] pub struct PostgresStore { pool: PgPool, @@ -229,6 +341,237 @@ RETURNING resource_version, created_at_ms, updated_at_ms } } + #[allow(clippy::too_many_arguments)] + pub async fn put_if_with_operation( + &self, + object_type: &str, + id: &str, + name: &str, + workspace: &str, + payload: &[u8], + condition: WriteCondition, + operation_record: &crate::storage_proto::StoredConfigUpdateOperation, + sandbox_projection: Option<&AtomicSandboxProjection<'_>>, + ) -> PersistenceResult { + let now_ms = current_time_ms(); + let mut tx = self + .pool + .begin() + .await + .map_err(|error| map_db_error(&error))?; + let sandbox_id = operation_record + .operation + .as_ref() + .ok_or_else(|| { + PersistenceError::Encode("update operation payload missing".to_string()) + })? + .sandbox_id + .clone(); + lock_sandbox_config_fence(&mut tx, &sandbox_id).await?; + let operation_record = + operation_with_current_policy_target(&mut tx, operation_record).await?; + let row = match condition { + WriteCondition::MustCreate => sqlx::query( + r" +INSERT INTO objects (object_type, id, name, workspace, payload, created_at_ms, updated_at_ms, labels, resource_version) +VALUES ($1, $2, $3, $4, $5, $6, $6, '{}'::jsonb, 1) +RETURNING resource_version, created_at_ms, updated_at_ms +", + ) + .bind(object_type) + .bind(id) + .bind(name) + .bind(workspace) + .bind(payload) + .bind(now_ms) + .fetch_one(&mut *tx) + .await + .map_err(|error| map_db_error(&error))?, + WriteCondition::MatchResourceVersion(expected) => sqlx::query( + r" +UPDATE objects +SET payload = $4, updated_at_ms = $5, resource_version = resource_version + 1 +WHERE object_type = $1 AND id = $2 AND resource_version = $3 +RETURNING resource_version, created_at_ms, updated_at_ms +", + ) + .bind(object_type) + .bind(id) + .bind(i64::try_from(expected).unwrap_or(i64::MAX)) + .bind(payload) + .bind(now_ms) + .fetch_optional(&mut *tx) + .await + .map_err(|error| map_db_error(&error))? + .ok_or(PersistenceError::Conflict { + current_resource_version: None, + })?, + WriteCondition::Unconditional => { + return Err(PersistenceError::Config( + "atomic settings operation requires a CAS condition".to_string(), + )); + } + }; + if let Some(projection) = sandbox_projection { + let result = sqlx::query( + r" +UPDATE objects +SET payload = $2, updated_at_ms = $3, resource_version = resource_version + 1 +WHERE object_type = 'sandbox' AND id = $1 AND resource_version = $4 +", + ) + .bind(projection.sandbox_id) + .bind(projection.payload) + .bind(now_ms) + .bind(i64::try_from(projection.expected_resource_version).unwrap_or(i64::MAX)) + .execute(&mut *tx) + .await + .map_err(|error| map_db_error(&error))?; + if result.rows_affected() != 1 { + return Err(PersistenceError::Conflict { + current_resource_version: None, + }); + } + } + insert_update_operation_postgres(&mut tx, &operation_record, now_ms).await?; + tx.commit().await.map_err(|error| map_db_error(&error))?; + let resource_version: i64 = row.try_get("resource_version").unwrap_or(1); + Ok(WriteResult { + resource_version: resource_version.max(1).cast_unsigned(), + created_at_ms: row.get("created_at_ms"), + updated_at_ms: row.get("updated_at_ms"), + }) + } + + pub async fn update_config_operation_cas( + &self, + record: &crate::storage_proto::StoredConfigUpdateOperation, + expected_resource_version: u64, + ) -> PersistenceResult> { + let metadata = record.metadata.as_ref().ok_or_else(|| { + PersistenceError::Encode("update operation metadata missing".to_string()) + })?; + let operation = record.operation.as_ref().ok_or_else(|| { + PersistenceError::Encode("update operation payload missing".to_string()) + })?; + let state = openshell_core::proto::ConfigUpdateOperationState::try_from(operation.state) + .unwrap_or_default(); + let row = sqlx::query( + r" +UPDATE objects +SET payload = $4, status = $5, next_attempt_at_ms = $6, + updated_at_ms = $7, resource_version = resource_version + 1 +WHERE object_type = $1 AND id = $2 AND resource_version = $3 +RETURNING resource_version +", + ) + .bind(crate::config_update_operation::CONFIG_UPDATE_OPERATION_OBJECT_TYPE) + .bind(&metadata.id) + .bind(i64::try_from(expected_resource_version).unwrap_or(i64::MAX)) + .bind(record.encode_to_vec()) + .bind(state.as_str_name()) + .bind(record.next_attempt_at_ms) + .bind(current_time_ms()) + .fetch_optional(&self.pool) + .await + .map_err(|error| map_db_error(&error))?; + Ok(row.map(|row| { + let version: i64 = row.get("resource_version"); + version.max(1).cast_unsigned() + })) + } + + pub async fn repair_config_operation_projection( + &self, + record: &crate::storage_proto::StoredConfigUpdateOperation, + expected_resource_version: u64, + ) -> PersistenceResult { + let metadata = record.metadata.as_ref().ok_or_else(|| { + PersistenceError::Encode("update operation metadata missing".to_string()) + })?; + let operation = record.operation.as_ref().ok_or_else(|| { + PersistenceError::Encode("update operation payload missing".to_string()) + })?; + let state = openshell_core::proto::ConfigUpdateOperationState::try_from(operation.state) + .unwrap_or_default(); + let result = sqlx::query( + r" +UPDATE objects +SET scope = $4, status = $5, next_attempt_at_ms = $6 +WHERE object_type = $1 AND id = $2 AND resource_version = $3 +", + ) + .bind(crate::config_update_operation::CONFIG_UPDATE_OPERATION_OBJECT_TYPE) + .bind(&metadata.id) + .bind(i64::try_from(expected_resource_version).unwrap_or(i64::MAX)) + .bind(&operation.sandbox_id) + .bind(state.as_str_name()) + .bind(record.next_attempt_at_ms) + .execute(&self.pool) + .await + .map_err(|error| map_db_error(&error))?; + Ok(result.rows_affected() == 1) + } + + pub async fn list_pending_config_operations_for_scope( + &self, + scope: &str, + ) -> PersistenceResult> { + let rows = sqlx::query( + r" +SELECT object_type, id, name, workspace, payload, created_at_ms, updated_at_ms, + labels, resource_version +FROM objects +WHERE object_type = $1 AND status = $2 AND scope = $3 +ORDER BY created_at_ms ASC, id ASC +", + ) + .bind(crate::config_update_operation::CONFIG_UPDATE_OPERATION_OBJECT_TYPE) + .bind(openshell_core::proto::ConfigUpdateOperationState::Pending.as_str_name()) + .bind(scope) + .fetch_all(&self.pool) + .await + .map_err(|error| map_db_error(&error))?; + Ok(rows.into_iter().map(row_to_object_record).collect()) + } + + pub async fn list_due_config_update_operations( + &self, + now_ms: i64, + limit: u32, + ) -> PersistenceResult> { + let rows = sqlx::query( + r" +SELECT object_type, id, name, workspace, payload, created_at_ms, updated_at_ms, + labels, resource_version +FROM objects +WHERE object_type = $1 AND status = $2 AND next_attempt_at_ms <= $3 +ORDER BY next_attempt_at_ms ASC, id ASC +LIMIT $4 +", + ) + .bind(crate::config_update_operation::CONFIG_UPDATE_OPERATION_OBJECT_TYPE) + .bind(openshell_core::proto::ConfigUpdateOperationState::Pending.as_str_name()) + .bind(now_ms) + .bind(i64::from(limit)) + .fetch_all(&self.pool) + .await + .map_err(|error| map_db_error(&error))?; + Ok(rows.into_iter().map(row_to_object_record).collect()) + } + + pub async fn count_pending_config_update_operations(&self) -> PersistenceResult { + let count: i64 = sqlx::query_scalar( + "SELECT COUNT(*) FROM objects WHERE object_type = $1 AND status = $2", + ) + .bind(crate::config_update_operation::CONFIG_UPDATE_OPERATION_OBJECT_TYPE) + .bind(openshell_core::proto::ConfigUpdateOperationState::Pending.as_str_name()) + .fetch_one(&self.pool) + .await + .map_err(|error| map_db_error(&error))?; + Ok(count.max(0).cast_unsigned()) + } + pub async fn delete_if( &self, object_type: &str, @@ -1002,6 +1345,8 @@ ON CONFLICT DO NOTHING let wrapped_payload = policy_payload_from_record(&record)?; let mut tx = self.pool.begin().await.map_err(|e| map_db_error(&e))?; + lock_sandbox_config_fence(&mut tx, &write.sandbox_id).await?; + let row = sqlx::query( r" SELECT payload, resource_version @@ -1069,6 +1414,21 @@ VALUES ($1, $2, $3, $4, $5, $6, $7, $7, $8) .await .map_err(|e| map_db_error(&e))?; + if let Some(operation_record) = write.operation.as_ref() { + let sandbox_name = sandbox + .metadata + .as_ref() + .map_or("", |metadata| metadata.name.as_str()); + let operation_record = operation_with_current_settings_target( + &mut tx, + operation_record, + &write.workspace, + sandbox_name, + ) + .await?; + insert_update_operation_postgres(&mut tx, &operation_record, now_ms).await?; + } + sqlx::query( r" UPDATE objects diff --git a/crates/openshell-server/src/persistence/sqlite.rs b/crates/openshell-server/src/persistence/sqlite.rs index fe506f3c1f..87dd7ee5db 100644 --- a/crates/openshell-server/src/persistence/sqlite.rs +++ b/crates/openshell-server/src/persistence/sqlite.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use super::{ - DraftChunkRecord, ObjectCursor, ObjectListQuery, ObjectRecord, PersistenceError, - PersistenceResult, PolicyRecord, WriteCondition, WriteResult, current_time_ms, map_db_error, - map_migrate_error, + AtomicSandboxProjection, DraftChunkRecord, ObjectCursor, ObjectListQuery, ObjectRecord, + PersistenceError, PersistenceResult, PolicyRecord, WriteCondition, WriteResult, + current_time_ms, map_db_error, map_migrate_error, }; use crate::policy_store::{ AtomicPolicyRevisionWrite, draft_chunk_payload_from_record, draft_chunk_record_from_parts, @@ -36,6 +36,113 @@ static IN_MEMORY_DB_SEQUENCE: AtomicU64 = AtomicU64::new(0); use super::{DELETE_MANY_BATCH_SIZE, DRAFT_CHUNK_OBJECT_TYPE, POLICY_OBJECT_TYPE}; +async fn insert_update_operation_sqlite( + tx: &mut sqlx::Transaction<'_, Sqlite>, + record: &crate::storage_proto::StoredConfigUpdateOperation, + now_ms: i64, +) -> PersistenceResult<()> { + let metadata = record + .metadata + .as_ref() + .ok_or_else(|| PersistenceError::Encode("update operation metadata missing".to_string()))?; + let operation = record + .operation + .as_ref() + .ok_or_else(|| PersistenceError::Encode("update operation payload missing".to_string()))?; + let state = openshell_core::proto::ConfigUpdateOperationState::try_from(operation.state) + .unwrap_or_default(); + sqlx::query( + r#" +INSERT INTO "objects" ( + "object_type", "id", "name", "workspace", "scope", "version", "status", "payload", + "created_at_ms", "updated_at_ms", "labels", "resource_version", "next_attempt_at_ms" +) +VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?9, '{}', 1, ?10) +"#, + ) + .bind(crate::config_update_operation::CONFIG_UPDATE_OPERATION_OBJECT_TYPE) + .bind(&metadata.id) + .bind(&metadata.name) + .bind(&metadata.workspace) + .bind(&operation.sandbox_id) + .bind(Option::::None) + .bind(state.as_str_name()) + .bind(record.encode_to_vec()) + .bind(now_ms) + .bind(record.next_attempt_at_ms) + .execute(&mut **tx) + .await + .map_err(|error| map_db_error(&error))?; + Ok(()) +} + +async fn lock_sandbox_config_fence( + tx: &mut sqlx::Transaction<'_, Sqlite>, + sandbox_id: &str, +) -> PersistenceResult<()> { + sqlx::query( + r#"INSERT INTO "sandbox_config_fences" ("sandbox_id") VALUES (?1) ON CONFLICT DO NOTHING"#, + ) + .bind(sandbox_id) + .execute(&mut **tx) + .await + .map_err(|error| map_db_error(&error))?; + Ok(()) +} + +async fn operation_with_current_policy_target( + tx: &mut sqlx::Transaction<'_, Sqlite>, + record: &crate::storage_proto::StoredConfigUpdateOperation, +) -> PersistenceResult { + let sandbox_id = record + .operation + .as_ref() + .ok_or_else(|| PersistenceError::Encode("update operation payload missing".to_string()))? + .sandbox_id + .as_str(); + let version: Option = sqlx::query_scalar( + r#"SELECT "version" FROM "objects" WHERE "object_type" = ?1 AND "scope" = ?2 ORDER BY "version" DESC LIMIT 1"#, + ) + .bind(POLICY_OBJECT_TYPE) + .bind(sandbox_id) + .fetch_optional(&mut **tx) + .await + .map_err(|error| map_db_error(&error))?; + let mut record = record.clone(); + record.target_policy_version = + version.map_or(0, |value| u32::try_from(value).unwrap_or(u32::MAX)); + Ok(record) +} + +async fn operation_with_current_settings_target( + tx: &mut sqlx::Transaction<'_, Sqlite>, + record: &crate::storage_proto::StoredConfigUpdateOperation, + workspace: &str, + sandbox_name: &str, +) -> PersistenceResult { + let payload: Option> = sqlx::query_scalar( + r#"SELECT "payload" FROM "objects" WHERE "object_type" = ?1 AND "workspace" = ?2 AND "name" = ?3"#, + ) + .bind(crate::grpc::policy::SANDBOX_SETTINGS_OBJECT_TYPE) + .bind(workspace) + .bind(sandbox_name) + .fetch_optional(&mut **tx) + .await + .map_err(|error| map_db_error(&error))?; + let revision = payload + .as_deref() + .map(serde_json::from_slice::) + .transpose() + .map_err(|error| { + PersistenceError::Decode(format!("decode settings payload failed: {error}")) + })? + .and_then(|value| value.get("revision").and_then(serde_json::Value::as_u64)) + .unwrap_or(0); + let mut record = record.clone(); + record.target_settings_revision = revision; + Ok(record) +} + #[derive(Debug, Clone)] pub struct SqliteStore { pool: SqlitePool, @@ -321,6 +428,238 @@ ON CONFLICT ("object_type", "workspace", "name") WHERE "name" IS NOT NULL DO UPD } } + #[allow(clippy::too_many_arguments)] + pub async fn put_if_with_operation( + &self, + object_type: &str, + id: &str, + name: &str, + workspace: &str, + payload: &[u8], + condition: WriteCondition, + operation_record: &crate::storage_proto::StoredConfigUpdateOperation, + sandbox_projection: Option<&AtomicSandboxProjection<'_>>, + ) -> PersistenceResult { + let now_ms = current_time_ms(); + let mut tx = self + .pool + .begin_with("BEGIN IMMEDIATE") + .await + .map_err(|error| map_db_error(&error))?; + let sandbox_id = operation_record + .operation + .as_ref() + .ok_or_else(|| { + PersistenceError::Encode("update operation payload missing".to_string()) + })? + .sandbox_id + .clone(); + lock_sandbox_config_fence(&mut tx, &sandbox_id).await?; + let operation_record = + operation_with_current_policy_target(&mut tx, operation_record).await?; + let resource_version = match condition { + WriteCondition::MustCreate => { + sqlx::query( + r#" +INSERT INTO "objects" ("object_type", "id", "name", "workspace", "payload", "created_at_ms", "updated_at_ms", "labels", "resource_version") +VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?6, '{}', 1) +"#, + ) + .bind(object_type) + .bind(id) + .bind(name) + .bind(workspace) + .bind(payload) + .bind(now_ms) + .execute(&mut *tx) + .await + .map_err(|error| map_db_error(&error))?; + 1 + } + WriteCondition::MatchResourceVersion(expected) => { + let result = sqlx::query( + r#" +UPDATE "objects" +SET "payload" = ?4, "updated_at_ms" = ?5, "resource_version" = "resource_version" + 1 +WHERE "object_type" = ?1 AND "id" = ?2 AND "resource_version" = ?3 +"#, + ) + .bind(object_type) + .bind(id) + .bind(i64::try_from(expected).unwrap_or(i64::MAX)) + .bind(payload) + .bind(now_ms) + .execute(&mut *tx) + .await + .map_err(|error| map_db_error(&error))?; + if result.rows_affected() != 1 { + return Err(PersistenceError::Conflict { + current_resource_version: None, + }); + } + expected.saturating_add(1) + } + WriteCondition::Unconditional => { + return Err(PersistenceError::Config( + "atomic settings operation requires a CAS condition".to_string(), + )); + } + }; + if let Some(projection) = sandbox_projection { + let result = sqlx::query( + r#" +UPDATE "objects" +SET "payload" = ?2, "updated_at_ms" = ?3, "resource_version" = "resource_version" + 1 +WHERE "object_type" = 'sandbox' AND "id" = ?1 AND "resource_version" = ?4 +"#, + ) + .bind(projection.sandbox_id) + .bind(projection.payload) + .bind(now_ms) + .bind(i64::try_from(projection.expected_resource_version).unwrap_or(i64::MAX)) + .execute(&mut *tx) + .await + .map_err(|error| map_db_error(&error))?; + if result.rows_affected() != 1 { + return Err(PersistenceError::Conflict { + current_resource_version: None, + }); + } + } + insert_update_operation_sqlite(&mut tx, &operation_record, now_ms).await?; + tx.commit().await.map_err(|error| map_db_error(&error))?; + Ok(WriteResult { + resource_version, + created_at_ms: now_ms, + updated_at_ms: now_ms, + }) + } + + pub async fn update_config_operation_cas( + &self, + record: &crate::storage_proto::StoredConfigUpdateOperation, + expected_resource_version: u64, + ) -> PersistenceResult> { + let metadata = record.metadata.as_ref().ok_or_else(|| { + PersistenceError::Encode("update operation metadata missing".to_string()) + })?; + let operation = record.operation.as_ref().ok_or_else(|| { + PersistenceError::Encode("update operation payload missing".to_string()) + })?; + let state = openshell_core::proto::ConfigUpdateOperationState::try_from(operation.state) + .unwrap_or_default(); + let result = sqlx::query( + r#" +UPDATE "objects" +SET "payload" = ?4, "status" = ?5, "next_attempt_at_ms" = ?6, + "updated_at_ms" = ?7, "resource_version" = "resource_version" + 1 +WHERE "object_type" = ?1 AND "id" = ?2 AND "resource_version" = ?3 +"#, + ) + .bind(crate::config_update_operation::CONFIG_UPDATE_OPERATION_OBJECT_TYPE) + .bind(&metadata.id) + .bind(i64::try_from(expected_resource_version).unwrap_or(i64::MAX)) + .bind(record.encode_to_vec()) + .bind(state.as_str_name()) + .bind(record.next_attempt_at_ms) + .bind(current_time_ms()) + .execute(&self.pool) + .await + .map_err(|error| map_db_error(&error))?; + Ok((result.rows_affected() == 1).then(|| expected_resource_version.saturating_add(1))) + } + + pub async fn repair_config_operation_projection( + &self, + record: &crate::storage_proto::StoredConfigUpdateOperation, + expected_resource_version: u64, + ) -> PersistenceResult { + let metadata = record.metadata.as_ref().ok_or_else(|| { + PersistenceError::Encode("update operation metadata missing".to_string()) + })?; + let operation = record.operation.as_ref().ok_or_else(|| { + PersistenceError::Encode("update operation payload missing".to_string()) + })?; + let state = openshell_core::proto::ConfigUpdateOperationState::try_from(operation.state) + .unwrap_or_default(); + let result = sqlx::query( + r#" +UPDATE "objects" +SET "scope" = ?4, "status" = ?5, "next_attempt_at_ms" = ?6 +WHERE "object_type" = ?1 AND "id" = ?2 AND "resource_version" = ?3 +"#, + ) + .bind(crate::config_update_operation::CONFIG_UPDATE_OPERATION_OBJECT_TYPE) + .bind(&metadata.id) + .bind(i64::try_from(expected_resource_version).unwrap_or(i64::MAX)) + .bind(&operation.sandbox_id) + .bind(state.as_str_name()) + .bind(record.next_attempt_at_ms) + .execute(&self.pool) + .await + .map_err(|error| map_db_error(&error))?; + Ok(result.rows_affected() == 1) + } + + pub async fn list_pending_config_operations_for_scope( + &self, + scope: &str, + ) -> PersistenceResult> { + let rows = sqlx::query( + r#" +SELECT "object_type", "id", "name", "workspace", "payload", "created_at_ms", "updated_at_ms", + "labels", "resource_version" +FROM "objects" +WHERE "object_type" = ?1 AND "status" = ?2 AND "scope" = ?3 +ORDER BY "created_at_ms" ASC, "id" ASC +"#, + ) + .bind(crate::config_update_operation::CONFIG_UPDATE_OPERATION_OBJECT_TYPE) + .bind(openshell_core::proto::ConfigUpdateOperationState::Pending.as_str_name()) + .bind(scope) + .fetch_all(&self.pool) + .await + .map_err(|error| map_db_error(&error))?; + Ok(rows.into_iter().map(row_to_object_record).collect()) + } + + pub async fn list_due_config_update_operations( + &self, + now_ms: i64, + limit: u32, + ) -> PersistenceResult> { + let rows = sqlx::query( + r#" +SELECT "object_type", "id", "name", "workspace", "payload", "created_at_ms", "updated_at_ms", + "labels", "resource_version" +FROM "objects" +WHERE "object_type" = ?1 AND "status" = ?2 AND "next_attempt_at_ms" <= ?3 +ORDER BY "next_attempt_at_ms" ASC, "id" ASC +LIMIT ?4 +"#, + ) + .bind(crate::config_update_operation::CONFIG_UPDATE_OPERATION_OBJECT_TYPE) + .bind(openshell_core::proto::ConfigUpdateOperationState::Pending.as_str_name()) + .bind(now_ms) + .bind(i64::from(limit)) + .fetch_all(&self.pool) + .await + .map_err(|error| map_db_error(&error))?; + Ok(rows.into_iter().map(row_to_object_record).collect()) + } + + pub async fn count_pending_config_update_operations(&self) -> PersistenceResult { + let count: i64 = sqlx::query_scalar( + r#"SELECT COUNT(*) FROM "objects" WHERE "object_type" = ?1 AND "status" = ?2"#, + ) + .bind(crate::config_update_operation::CONFIG_UPDATE_OPERATION_OBJECT_TYPE) + .bind(openshell_core::proto::ConfigUpdateOperationState::Pending.as_str_name()) + .fetch_one(&self.pool) + .await + .map_err(|error| map_db_error(&error))?; + Ok(count.max(0).cast_unsigned()) + } + pub async fn delete_if( &self, object_type: &str, @@ -1131,6 +1470,8 @@ ON CONFLICT DO NOTHING .await .map_err(|e| map_db_error(&e))?; + lock_sandbox_config_fence(&mut tx, &write.sandbox_id).await?; + let row = sqlx::query( r#" SELECT "payload", "resource_version" @@ -1197,6 +1538,21 @@ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?7, ?8) .await .map_err(|e| map_db_error(&e))?; + if let Some(operation_record) = write.operation.as_ref() { + let sandbox_name = sandbox + .metadata + .as_ref() + .map_or("", |metadata| metadata.name.as_str()); + let operation_record = operation_with_current_settings_target( + &mut tx, + operation_record, + &write.workspace, + sandbox_name, + ) + .await?; + insert_update_operation_sqlite(&mut tx, &operation_record, now_ms).await?; + } + sqlx::query( r#" UPDATE "objects" diff --git a/crates/openshell-server/src/persistence/tests.rs b/crates/openshell-server/src/persistence/tests.rs index 1a149ed7ce..daf2550412 100644 --- a/crates/openshell-server/src/persistence/tests.rs +++ b/crates/openshell-server/src/persistence/tests.rs @@ -2,8 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use super::{ - ObjectListQuery, ObjectType, PersistenceError, PolicyRecord, Store, generate_name, test_store, + ObjectId, ObjectListQuery, ObjectName, ObjectType, PersistenceError, PolicyRecord, Store, + generate_name, test_store, }; +use crate::config_update_operation::{CommittedResponse, OperationTarget, new_record}; use crate::policy_store::{AtomicPolicyRevisionWrite, PolicyStoreExt}; use openshell_core::proto::datamodel::v1::ObjectMeta as ProtoObjectMeta; use openshell_core::proto::{ObjectForTest, Sandbox, SandboxPolicy, SandboxSpec}; @@ -226,6 +228,20 @@ fn embedded_migrators_include_pagination_indexes() { } } +#[test] +fn embedded_migrators_include_config_operation_query_support() { + for (backend, migration) in [ + ("sqlite", super::sqlite::embedded_migration_sql(9)), + ("postgres", super::postgres::embedded_migration_sql(9)), + ] { + let sql = + migration.unwrap_or_else(|| panic!("{backend} migrator is missing migration 009")); + assert!(sql.contains("sandbox_config_fences")); + assert!(sql.contains("objects_type_status_due_idx")); + assert!(sql.contains("next_attempt_at_ms")); + } +} + #[tokio::test] async fn sqlite_in_memory_store_survives_pool_connection_replacement() { for url in ["sqlite::memory:", "sqlite://?mode=memory"] { @@ -1093,6 +1109,545 @@ fn policy_test_sandbox(id: &str, name: &str) -> Sandbox { } } +fn config_operation_for( + sandbox: &Sandbox, + policy_version: u32, + settings_revision: u64, +) -> crate::storage_proto::StoredConfigUpdateOperation { + new_record( + sandbox, + "default", + "", + OperationTarget { + policy_version, + settings_revision, + }, + CommittedResponse::default(), + ) +} + +#[tokio::test] +async fn operation_cas_updates_sql_state_and_due_index_together() { + use openshell_core::proto::ConfigUpdateOperationState; + + let store = test_store().await; + let sandbox = policy_test_sandbox("operation-state-sandbox", "operation-state-sandbox"); + store.put_message(&sandbox).await.unwrap(); + let operation = config_operation_for(&sandbox, 0, 1); + let operation_id = operation.operation.as_ref().unwrap().operation_id.clone(); + store + .put_if_with_operation( + crate::grpc::policy::SANDBOX_SETTINGS_OBJECT_TYPE, + "operation-state-settings", + sandbox.object_name(), + "default", + br#"{"revision":1,"settings":{}}"#, + super::WriteCondition::MustCreate, + &operation, + None, + ) + .await + .unwrap(); + + let pending = store + .list_pending_config_operations_for_scope(sandbox.object_id()) + .await + .unwrap(); + assert_eq!(pending.len(), 1); + let mut terminal = pending[0].clone(); + terminal.operation.as_mut().unwrap().state = ConfigUpdateOperationState::Applied.into(); + let version = terminal.metadata.as_ref().unwrap().resource_version; + let updated = store + .update_config_operation_cas(&terminal, version) + .await + .unwrap(); + assert!(matches!(updated, super::KnownVersionUpdate::Changed(_))); + assert!( + store + .list_pending_config_operations_for_scope(sandbox.object_id()) + .await + .unwrap() + .is_empty() + ); + let stored = store + .get_message::(&operation_id) + .await + .unwrap() + .unwrap(); + assert_eq!( + ConfigUpdateOperationState::try_from(stored.operation.unwrap().state).unwrap(), + ConfigUpdateOperationState::Applied + ); +} + +#[tokio::test] +async fn operation_projection_repair_decodes_authoritative_payload_without_version_churn() { + use openshell_core::proto::ConfigUpdateOperationState; + + let store = test_store().await; + let sandbox = policy_test_sandbox("operation-repair-sandbox", "operation-repair-sandbox"); + store.put_message(&sandbox).await.unwrap(); + let operation = config_operation_for(&sandbox, 0, 1); + let operation_id = operation.operation.as_ref().unwrap().operation_id.clone(); + store + .put_if_with_operation( + crate::grpc::policy::SANDBOX_SETTINGS_OBJECT_TYPE, + "operation-repair-settings", + sandbox.object_name(), + "default", + br#"{"revision":1,"settings":{}}"#, + super::WriteCondition::MustCreate, + &operation, + None, + ) + .await + .unwrap(); + + let terminal = store + .update_message_cas::( + &operation_id, + 0, + |record| { + record.operation.as_mut().unwrap().state = + ConfigUpdateOperationState::Applied.into(); + }, + ) + .await + .unwrap(); + assert_eq!( + store + .list_pending_config_operations_for_scope(sandbox.object_id()) + .await + .unwrap() + .len(), + 1, + "generic legacy writes leave the SQL projection stale" + ); + + let version = terminal.metadata.as_ref().unwrap().resource_version; + assert!( + store + .repair_config_operation_projection(&terminal, version) + .await + .unwrap() + ); + assert!( + store + .list_pending_config_operations_for_scope(sandbox.object_id()) + .await + .unwrap() + .is_empty() + ); + let repaired = store + .get_message::(&operation_id) + .await + .unwrap() + .unwrap(); + assert_eq!(repaired.metadata.unwrap().resource_version, version); +} + +#[tokio::test] +async fn pending_operation_queries_are_scoped_bounded_and_due_ordered() { + let store = test_store().await; + let first_sandbox = policy_test_sandbox("operation-query-a", "operation-query-a"); + let second_sandbox = policy_test_sandbox("operation-query-b", "operation-query-b"); + store.put_message(&first_sandbox).await.unwrap(); + store.put_message(&second_sandbox).await.unwrap(); + + let mut first = config_operation_for(&first_sandbox, 0, 1); + first.next_attempt_at_ms = 10; + let first_id = first.operation.as_ref().unwrap().operation_id.clone(); + let mut second = config_operation_for(&second_sandbox, 0, 1); + second.next_attempt_at_ms = 10; + let second_id = second.operation.as_ref().unwrap().operation_id.clone(); + for (index, (sandbox, operation)) in [(&first_sandbox, &first), (&second_sandbox, &second)] + .into_iter() + .enumerate() + { + store + .put_if_with_operation( + crate::grpc::policy::SANDBOX_SETTINGS_OBJECT_TYPE, + &format!("operation-query-settings-{index}"), + sandbox.object_name(), + "default", + br#"{"revision":1,"settings":{}}"#, + super::WriteCondition::MustCreate, + operation, + None, + ) + .await + .unwrap(); + } + + let scoped = store + .list_pending_config_operations_for_scope(first_sandbox.object_id()) + .await + .unwrap(); + assert_eq!(scoped.len(), 1); + assert_eq!(scoped[0].operation.as_ref().unwrap().operation_id, first_id); + + let due = store + .list_due_config_update_operations(i64::MAX, 1) + .await + .unwrap(); + assert_eq!(due.len(), 1); + let first_page_id = due[0].operation.as_ref().unwrap().operation_id.clone(); + assert!([&first_id, &second_id].contains(&&first_page_id)); + + let mut completed = due[0].clone(); + completed.operation.as_mut().unwrap().state = + openshell_core::proto::ConfigUpdateOperationState::Applied.into(); + let version = completed.metadata.as_ref().unwrap().resource_version; + store + .update_config_operation_cas(&completed, version) + .await + .unwrap(); + let next_page = store + .list_due_config_update_operations(i64::MAX, 1) + .await + .unwrap(); + assert_eq!(next_page.len(), 1); + let second_page_id = &next_page[0].operation.as_ref().unwrap().operation_id; + assert_ne!(second_page_id, &first_page_id); + assert!([&first_id, &second_id].contains(&second_page_id)); +} + +#[tokio::test] +async fn configuration_transactions_fill_the_other_target_dimension() { + let store = test_store().await; + + let settings_sandbox = policy_test_sandbox("target-settings-write", "target-settings-write"); + store.put_message(&settings_sandbox).await.unwrap(); + store + .put_policy_revision( + "target-settings-policy", + settings_sandbox.object_id(), + "default", + 1, + &SandboxPolicy::default().encode_to_vec(), + "target-settings-policy-hash", + ) + .await + .unwrap(); + let settings_operation = config_operation_for(&settings_sandbox, 0, 1); + let settings_operation_id = settings_operation + .operation + .as_ref() + .unwrap() + .operation_id + .clone(); + store + .put_if_with_operation( + crate::grpc::policy::SANDBOX_SETTINGS_OBJECT_TYPE, + "target-settings-record", + settings_sandbox.object_name(), + "default", + br#"{"revision":1,"settings":{}}"#, + super::WriteCondition::MustCreate, + &settings_operation, + None, + ) + .await + .unwrap(); + let settings_operation = store + .get_message::(&settings_operation_id) + .await + .unwrap() + .unwrap(); + assert_eq!(settings_operation.target_policy_version, 1); + assert_eq!(settings_operation.target_settings_revision, 1); + + let policy_sandbox = policy_test_sandbox("target-policy-write", "target-policy-write"); + store.put_message(&policy_sandbox).await.unwrap(); + let settings_seed = config_operation_for(&policy_sandbox, 0, 2); + store + .put_if_with_operation( + crate::grpc::policy::SANDBOX_SETTINGS_OBJECT_TYPE, + "target-policy-settings-record", + policy_sandbox.object_name(), + "default", + br#"{"revision":2,"settings":{}}"#, + super::WriteCondition::MustCreate, + &settings_seed, + None, + ) + .await + .unwrap(); + let current = store + .get_message::(policy_sandbox.object_id()) + .await + .unwrap() + .unwrap(); + let policy_operation = config_operation_for(&policy_sandbox, 1, 0); + let policy_operation_id = policy_operation + .operation + .as_ref() + .unwrap() + .operation_id + .clone(); + store + .put_policy_revision_atomic(&AtomicPolicyRevisionWrite { + id: "target-policy-revision".to_string(), + sandbox_id: policy_sandbox.object_id().to_string(), + workspace: "default".to_string(), + version: 1, + policy_payload: SandboxPolicy::default().encode_to_vec(), + policy_hash: "target-policy-hash".to_string(), + provenance: StdHashMap::new(), + expected_resource_version: current.metadata.as_ref().unwrap().resource_version, + annotations: StdHashMap::new(), + backfill_policy: None, + operation: Some(policy_operation), + }) + .await + .unwrap(); + let policy_operation = store + .get_message::(&policy_operation_id) + .await + .unwrap() + .unwrap(); + assert_eq!(policy_operation.target_policy_version, 1); + assert_eq!(policy_operation.target_settings_revision, 2); +} + +#[tokio::test] +async fn operation_insert_failure_rolls_back_settings_write() { + let store = test_store().await; + let sandbox = policy_test_sandbox("operation-rollback", "operation-rollback"); + store.put_message(&sandbox).await.unwrap(); + let operation = new_record( + &sandbox, + "default", + "same-request", + OperationTarget { + policy_version: 0, + settings_revision: 1, + }, + CommittedResponse::default(), + ); + store + .put_if_with_operation( + crate::grpc::policy::SANDBOX_SETTINGS_OBJECT_TYPE, + "operation-rollback-settings", + sandbox.object_name(), + "default", + br#"{"revision":1,"settings":{}}"#, + super::WriteCondition::MustCreate, + &operation, + None, + ) + .await + .unwrap(); + let duplicate_operation = new_record( + &sandbox, + "default", + "same-request", + OperationTarget { + policy_version: 0, + settings_revision: 2, + }, + CommittedResponse::default(), + ); + let error = store + .put_if_with_operation( + crate::grpc::policy::SANDBOX_SETTINGS_OBJECT_TYPE, + "operation-rollback-settings", + sandbox.object_name(), + "default", + br#"{"revision":2,"settings":{}}"#, + super::WriteCondition::MatchResourceVersion(1), + &duplicate_operation, + None, + ) + .await + .unwrap_err(); + assert!(matches!(error, PersistenceError::UniqueViolation { .. })); + let settings = store + .get( + crate::grpc::policy::SANDBOX_SETTINGS_OBJECT_TYPE, + "operation-rollback-settings", + ) + .await + .unwrap() + .unwrap(); + assert_eq!(settings.payload, br#"{"revision":1,"settings":{}}"#); + assert_eq!(settings.resource_version, 1); +} + +#[tokio::test] +async fn operation_insert_failure_rolls_back_policy_and_projection() { + let store = test_store().await; + let sandbox = policy_test_sandbox("policy-operation-rollback", "policy-operation-rollback"); + store.put_message(&sandbox).await.unwrap(); + let existing_operation = new_record( + &sandbox, + "default", + "duplicate-policy-request", + OperationTarget { + policy_version: 0, + settings_revision: 1, + }, + CommittedResponse::default(), + ); + store + .put_if_with_operation( + crate::grpc::policy::SANDBOX_SETTINGS_OBJECT_TYPE, + "policy-operation-rollback-settings", + sandbox.object_name(), + "default", + br#"{"revision":1,"settings":{}}"#, + super::WriteCondition::MustCreate, + &existing_operation, + None, + ) + .await + .unwrap(); + let before = store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + let duplicate_operation = new_record( + &sandbox, + "default", + "duplicate-policy-request", + OperationTarget { + policy_version: 1, + settings_revision: 0, + }, + CommittedResponse::default(), + ); + let policy = SandboxPolicy::default(); + let error = store + .put_policy_revision_atomic(&AtomicPolicyRevisionWrite { + id: "policy-operation-rollback-revision".to_string(), + sandbox_id: sandbox.object_id().to_string(), + workspace: "default".to_string(), + version: 1, + policy_payload: policy.encode_to_vec(), + policy_hash: "rollback-hash".to_string(), + provenance: StdHashMap::new(), + expected_resource_version: before.metadata.as_ref().unwrap().resource_version, + annotations: StdHashMap::from([("changed".to_string(), "true".to_string())]), + backfill_policy: Some(policy), + operation: Some(duplicate_operation), + }) + .await + .unwrap_err(); + assert!(matches!(error, PersistenceError::UniqueViolation { .. })); + assert!( + store + .get_latest_policy(sandbox.object_id()) + .await + .unwrap() + .is_none() + ); + let after = store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!( + after.metadata.as_ref().unwrap().resource_version, + before.metadata.as_ref().unwrap().resource_version + ); + assert!(after.metadata.as_ref().unwrap().annotations.is_empty()); +} + +#[tokio::test] +#[ignore = "requires OPENSHELL_TEST_POSTGRES_URL pointing to a test database"] +async fn postgres_policy_and_settings_operations_allocate_serial_targets() { + let url = std::env::var("OPENSHELL_TEST_POSTGRES_URL").expect("test database URL"); + let first = Store::connect(&url).await.unwrap(); + let second = Store::connect(&url).await.unwrap(); + let sandbox_id = uuid::Uuid::new_v4().to_string(); + let sandbox = policy_test_sandbox(&sandbox_id, &sandbox_id); + first.put_message(&sandbox).await.unwrap(); + let current = first + .get_message::(&sandbox_id) + .await + .unwrap() + .unwrap(); + let settings_operation = config_operation_for(&sandbox, 0, 1); + let settings_operation_id = settings_operation + .operation + .as_ref() + .unwrap() + .operation_id + .clone(); + let policy_operation = config_operation_for(&sandbox, 1, 0); + let policy_operation_id = policy_operation + .operation + .as_ref() + .unwrap() + .operation_id + .clone(); + let barrier = std::sync::Arc::new(tokio::sync::Barrier::new(2)); + let settings_barrier = barrier.clone(); + let policy = SandboxPolicy::default(); + + let (settings_result, policy_result) = tokio::join!( + async { + settings_barrier.wait().await; + first + .put_if_with_operation( + crate::grpc::policy::SANDBOX_SETTINGS_OBJECT_TYPE, + &uuid::Uuid::new_v4().to_string(), + sandbox.object_name(), + "default", + br#"{"revision":1,"settings":{}}"#, + super::WriteCondition::MustCreate, + &settings_operation, + None, + ) + .await + }, + async { + barrier.wait().await; + second + .put_policy_revision_atomic(&AtomicPolicyRevisionWrite { + id: uuid::Uuid::new_v4().to_string(), + sandbox_id: sandbox_id.clone(), + workspace: "default".to_string(), + version: 1, + policy_payload: policy.encode_to_vec(), + policy_hash: "serial-target".to_string(), + provenance: StdHashMap::new(), + expected_resource_version: current.metadata.as_ref().unwrap().resource_version, + annotations: StdHashMap::new(), + backfill_policy: None, + operation: Some(policy_operation), + }) + .await + } + ); + settings_result.unwrap(); + policy_result.unwrap(); + + let settings = first + .get_message::(&settings_operation_id) + .await + .unwrap() + .unwrap(); + let policy = first + .get_message::(&policy_operation_id) + .await + .unwrap() + .unwrap(); + let targets = [ + ( + settings.target_policy_version, + settings.target_settings_revision, + ), + ( + policy.target_policy_version, + policy.target_settings_revision, + ), + ]; + assert!(targets.contains(&(1, 1)), "committed targets: {targets:?}"); + assert_ne!(targets, [(0, 1), (1, 0)]); +} + #[tokio::test] async fn initial_policy_history_is_insert_only() { assert_initial_policy_history_is_insert_only(&test_store().await).await; @@ -1203,6 +1758,7 @@ async fn policy_atomic_write_commits_revision_provenance_and_sandbox_projection( expected_resource_version: current_version, annotations: provenance.clone(), backfill_policy: Some(policy.clone()), + operation: None, }) .await .unwrap(); @@ -1272,6 +1828,7 @@ async fn policy_atomic_write_rolls_back_sandbox_when_revision_insert_conflicts() expected_resource_version: before_version, annotations: StdHashMap::from([("signature".to_string(), "new".to_string())]), backfill_policy: Some(policy), + operation: None, }) .await .unwrap_err(); @@ -1317,6 +1874,7 @@ async fn policy_atomic_write_persists_workspace() { expected_resource_version: current_version, annotations: StdHashMap::new(), backfill_policy: Some(policy), + operation: None, }) .await .unwrap(); diff --git a/crates/openshell-server/src/policy_store.rs b/crates/openshell-server/src/policy_store.rs index f097c2eb50..7a1f2601b2 100644 --- a/crates/openshell-server/src/policy_store.rs +++ b/crates/openshell-server/src/policy_store.rs @@ -4,7 +4,7 @@ use crate::persistence::{ DraftChunkRecord, PersistenceError, PersistenceResult, PolicyRecord, SetResourceVersion, Store, }; -use crate::storage_proto::{DraftChunkPayload, PolicyRevisionPayload}; +use crate::storage_proto::{DraftChunkPayload, PolicyRevisionPayload, StoredConfigUpdateOperation}; use openshell_core::proto::{NetworkPolicyRule, Sandbox, SandboxPolicy as ProtoSandboxPolicy}; use prost::Message; use std::collections::HashMap; @@ -35,6 +35,7 @@ pub struct AtomicPolicyRevisionWrite { pub expected_resource_version: u64, pub annotations: HashMap, pub backfill_policy: Option, + pub operation: Option, } pub fn policy_record_for_atomic_write( diff --git a/crates/openshell-server/src/sandbox_watch.rs b/crates/openshell-server/src/sandbox_watch.rs index ac38eba8db..7f761f4021 100644 --- a/crates/openshell-server/src/sandbox_watch.rs +++ b/crates/openshell-server/src/sandbox_watch.rs @@ -16,13 +16,16 @@ use tonic::Status; #[derive(Debug, Clone)] pub struct SandboxWatchBus { inner: Arc>>>, + all: broadcast::Sender, } impl SandboxWatchBus { #[must_use] pub fn new() -> Self { + let (all, _rx) = broadcast::channel(1024); Self { inner: Arc::new(Mutex::new(HashMap::new())), + all, } } @@ -42,6 +45,7 @@ impl SandboxWatchBus { pub fn notify(&self, sandbox_id: &str) { let tx = self.sender_for(sandbox_id); let _ = tx.send(()); + let _ = self.all.send(sandbox_id.to_string()); } /// Subscribe to sandbox updates. @@ -49,6 +53,11 @@ impl SandboxWatchBus { self.sender_for(sandbox_id).subscribe() } + /// Subscribe to sandbox ids whose persisted state changed. + pub fn subscribe_all(&self) -> broadcast::Receiver { + self.all.subscribe() + } + /// Remove the bus entry for the given sandbox id. /// /// This drops the broadcast sender, closing any active receivers with @@ -114,4 +123,12 @@ mod tests { // Should not panic bus.remove("nonexistent"); } + + #[test] + fn sandbox_watch_bus_global_subscription_receives_changed_id() { + let bus = SandboxWatchBus::new(); + let mut rx = bus.subscribe_all(); + bus.notify("sb-global"); + assert_eq!(rx.try_recv().unwrap(), "sb-global"); + } } diff --git a/crates/openshell-server/src/storage_proto.rs b/crates/openshell-server/src/storage_proto.rs index 46c681422e..e234cf85ef 100644 --- a/crates/openshell-server/src/storage_proto.rs +++ b/crates/openshell-server/src/storage_proto.rs @@ -150,6 +150,48 @@ impl ObjectWorkspace for StoredConfigComponentObservation { } } +impl ObjectId for StoredConfigUpdateOperation { + fn object_id(&self) -> &str { + self.metadata.as_ref().map_or("", |m| m.id.as_str()) + } +} + +impl ObjectName for StoredConfigUpdateOperation { + fn object_name(&self) -> &str { + self.metadata.as_ref().map_or("", |m| m.name.as_str()) + } +} + +impl ObjectLabels for StoredConfigUpdateOperation { + fn object_labels(&self) -> Option> { + self.metadata.as_ref().map(|m| m.labels.clone()) + } +} + +impl SetResourceVersion for StoredConfigUpdateOperation { + fn set_resource_version(&mut self, version: u64) { + if let Some(meta) = self.metadata.as_mut() { + meta.resource_version = version; + } + } +} + +impl GetResourceVersion for StoredConfigUpdateOperation { + fn get_resource_version(&self) -> u64 { + self.metadata.as_ref().map_or(0, |m| m.resource_version) + } +} + +impl ObjectWorkspace for StoredConfigUpdateOperation { + fn object_workspace(&self) -> &str { + self.metadata.as_ref().map_or("", |m| m.workspace.as_str()) + } + + fn requires_workspace() -> bool { + true + } +} + #[cfg(test)] mod tests { use super::*; @@ -159,13 +201,13 @@ mod tests { use std::collections::{BTreeMap, BTreeSet, VecDeque}; const STORAGE_V1_SCHEMA_SHA256: &str = - "a623124c961f3a56af58a4ab148c12985e5efaec1ad23441031f5cd5b073fe22"; + "643012c48073e4cc64f07d50e33a7e24048f112ffefd34e22c03aa5d90b60cfb"; const PUBLIC_RPC_SCHEMA_SHA256: &str = - "3ba470c3278d0c7a49b7dde7ee735017604afab5054fb7a95665c71a602121f1"; + "cfbcb07a39dd51d0f50aa5476f381e84a688364367b4cc42436d7a53e539215c"; const DURABLE_SCHEMA_SHA256: &str = - "96911a750b59ddd3eae48b4539ce1545cf5357ebe830143889ec975216522100"; + "9e7ef432aff1580782b05794de8b67d3701e674c688d16cea60e7af5bbc5db38"; const PUBLIC_DURABLE_OVERLAP_SHA256: &str = - "0d1b24b78becb0025512f35fe320b0d91d0728d3239eabf78cdf99e599eb6af2"; + "45854a0762542ffb1c5028551471ca85aa7cfb8e4aa7e35d12b20892fc564ef4"; // Synthetic payloads generated with the public declarations at v0.0.116, // before their relocation into openshell.storage.v1. Values are deliberately // non-secret and the ordinary protobuf bytes contain no package names. @@ -178,23 +220,25 @@ mod tests { "0a0472756c651a07666978747572652d0000403f3a0b6578616d706c652e636f6d40bb035002"; const V0_0_116_POLICY_RECORD: &str = "0a09706f6c6963792d6964120a73616e64626f782d6964180222030102032a0673686132353632066c6f616465643a046e6f6e6540fa0148ac0252110a06736f75726365120766697874757265"; const V0_0_116_DRAFT_RECORD: &str = "0a086368756e6b2d6964120a73616e64626f782d69641802220770656e64696e672a0472756c65320204053a076669787475726549000000000000e83f50de02589003620b6578616d706c652e636f6d68bb037801"; - const STORAGE_MESSAGE_NAMES: [&str; 8] = [ + const STORAGE_MESSAGE_NAMES: [&str; 9] = [ "DraftChunkPayload", "PolicyRevisionPayload", "StoredConfigComponentObservation", + "StoredConfigUpdateOperation", "StoredDraftChunk", "StoredPolicyRevision", "StoredProviderCredentialRefreshState", "StoredProviderProfile", "StoredRefreshMaterialDeletion", ]; - const DURABLE_ROOTS: [&str; 13] = [ + const DURABLE_ROOTS: [&str; 14] = [ ".openshell.datamodel.v1.Provider", ".openshell.datamodel.v1.Workspace", ".openshell.sandbox.v1.SandboxPolicy", ".openshell.storage.v1.DraftChunkPayload", ".openshell.storage.v1.PolicyRevisionPayload", ".openshell.storage.v1.StoredConfigComponentObservation", + ".openshell.storage.v1.StoredConfigUpdateOperation", ".openshell.storage.v1.StoredProviderCredentialRefreshState", ".openshell.storage.v1.StoredProviderProfile", ".openshell.v1.Sandbox", @@ -531,13 +575,13 @@ mod tests { assert_eq!( (public_closure.messages.len(), public_closure.enums.len()), - (291, 15) + (288, 17) ); assert_eq!( (durable_closure.messages.len(), durable_closure.enums.len()), - (84, 11) + (87, 12) ); - assert_eq!((overlap_messages.len(), overlap_enums.len()), (73, 11)); + assert_eq!((overlap_messages.len(), overlap_enums.len()), (74, 12)); assert_eq!( public_inventory_hash, PUBLIC_RPC_SCHEMA_SHA256, diff --git a/crates/openshell-server/src/supervisor_session.rs b/crates/openshell-server/src/supervisor_session.rs index da3c93248c..0e4bc0865e 100644 --- a/crates/openshell-server/src/supervisor_session.rs +++ b/crates/openshell-server/src/supervisor_session.rs @@ -17,6 +17,7 @@ use uuid::Uuid; #[cfg(test)] use openshell_core::proto::ConfigBootstrapResult; +use openshell_core::proto::SUPERVISOR_PROTOCOL_REVISION; use openshell_core::proto::{ ConfigApplyOutcome, ConfigBootstrap, ConfigComponent, ConfigComponentApplyResult, ConfigSnapshotRevision, ConfigUpdate, ConfigUpdateResult, GatewayMessage, PolicySource, @@ -24,10 +25,6 @@ use openshell_core::proto::{ Sandbox, SandboxPhase, SessionAccepted, SshRelayTarget, SupervisorMessage, config_snapshot_revision, config_update, gateway_message, relay_open, supervisor_message, }; -use openshell_core::proto::{ - LEGACY_SUPERVISOR_PROTOCOL_REVISION, PREVIOUS_SUPERVISOR_PROTOCOL_REVISION, - SUPERVISOR_PROTOCOL_REVISION, -}; use openshell_core::transport_errors::is_expected_transport_close_status; use openshell_core::{ObjectId, ObjectWorkspace}; @@ -164,6 +161,7 @@ fn build_config_update( policy_version: snapshot.version, policy_source: snapshot.policy_source, global_policy_version: snapshot.global_policy_version, + settings_revision: snapshot.settings_revision, }, )), }; @@ -208,6 +206,7 @@ fn config_message_revision(message: &SupervisorConfigMessage) -> ConfigSnapshotR policy_version: snapshot.version, policy_source: snapshot.policy_source, global_policy_version: snapshot.global_policy_version, + settings_revision: snapshot.settings_revision, }, )), }, @@ -1090,7 +1089,6 @@ pub async fn handle_connect_supervisor( }; let sandbox_id = hello.sandbox_id.clone(); - let stream_applies_config = hello.protocol_revision == SUPERVISOR_PROTOCOL_REVISION; if sandbox_id.is_empty() { return Err(Status::invalid_argument("sandbox_id is required")); } @@ -1100,44 +1098,36 @@ pub async fn handle_connect_supervisor( } let sandbox = require_persisted_sandbox(&state.store, &sandbox_id).await?; - let bootstrap_timeout = if stream_applies_config { - crate::config_delivery::REQUIRED_CONFIG_BOOTSTRAP_BUILD_TIMEOUT - } else { - crate::config_delivery::OPTIONAL_CONFIG_BOOTSTRAP_BUILD_TIMEOUT + let bootstrap = match crate::config_delivery::build_config_bootstrap( + state, + &sandbox, + crate::config_delivery::REQUIRED_CONFIG_BOOTSTRAP_BUILD_TIMEOUT, + ) + .await + { + Ok(bootstrap) => { + counter!( + "openshell_supervisor_config_bootstrap_total", + "outcome" => "built" + ) + .increment(1); + bootstrap + } + Err(error) => { + counter!( + "openshell_supervisor_config_bootstrap_total", + "outcome" => "build_failed" + ) + .increment(1); + warn!( + sandbox_id = %sandbox_id, + error_code = ?error.code(), + "failed to build supervisor configuration bootstrap" + ); + return Err(error); + } }; - let bootstrap = - match crate::config_delivery::build_config_bootstrap(state, &sandbox, bootstrap_timeout) - .await - { - Ok(bootstrap) => { - counter!( - "openshell_supervisor_config_bootstrap_total", - "outcome" => "built" - ) - .increment(1); - Some(bootstrap) - } - Err(error) => { - counter!( - "openshell_supervisor_config_bootstrap_total", - "outcome" => "build_failed" - ) - .increment(1); - warn!( - sandbox_id = %sandbox_id, - error_code = ?error.code(), - "failed to build supervisor configuration bootstrap" - ); - if stream_applies_config { - return Err(error); - } - None - } - }; - let expected_bootstrap_revisions = bootstrap - .as_ref() - .map(bootstrap_revision_fence) - .unwrap_or_default(); + let expected_bootstrap_revisions = bootstrap_revision_fence(&bootstrap); let session_id = Uuid::new_v4().to_string(); info!( @@ -1151,11 +1141,11 @@ pub async fn handle_connect_supervisor( // keeps a concurrent ConfigUpdate from becoming the first stream message. let (tx, rx) = mpsc::channel::(64); let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>(); - let mut accepted = GatewayMessage { + let accepted = GatewayMessage { payload: Some(gateway_message::Payload::SessionAccepted(SessionAccepted { session_id: session_id.clone(), heartbeat_interval_secs: HEARTBEAT_INTERVAL_SECS, - bootstrap, + bootstrap: Some(bootstrap), protocol_revision: hello.protocol_revision, })), }; @@ -1165,17 +1155,9 @@ pub async fn handle_connect_supervisor( "outcome" => "payload_too_large" ) .increment(1); - if stream_applies_config { - return Err(Status::resource_exhausted( - "supervisor configuration bootstrap exceeds the stream message limit", - )); - } - let Some(gateway_message::Payload::SessionAccepted(accepted_payload)) = - accepted.payload.as_mut() - else { - unreachable!("constructed SessionAccepted payload") - }; - accepted_payload.bootstrap = None; + return Err(Status::resource_exhausted( + "supervisor configuration bootstrap exceeds the stream message limit", + )); } if tx.send(accepted).await.is_err() { return Err(Status::internal("failed to send session accepted")); @@ -1202,11 +1184,6 @@ pub async fn handle_connect_supervisor( .await; } - if !stream_applies_config { - let _ = - mark_supervisor_initialized(state, &sandbox_id, &session_id, &hello.instance_id).await; - } - // Step 4: Spawn the session loop that reads inbound messages. let state_clone = Arc::clone(state); let sandbox_id_clone = sandbox_id.clone(); @@ -1217,7 +1194,6 @@ pub async fn handle_connect_supervisor( &sandbox_id_clone, &session_id, &instance_id, - stream_applies_config, &expected_bootstrap_revisions, &tx, &mut inbound, @@ -1258,28 +1234,13 @@ pub async fn handle_connect_supervisor( Ok(Response::new(stream)) } -fn validate_protocol_revision(sandbox_id: &str, supervisor_revision: u32) -> Result<(), Status> { - match supervisor_revision { - SUPERVISOR_PROTOCOL_REVISION => Ok(()), - PREVIOUS_SUPERVISOR_PROTOCOL_REVISION => { - counter!("openshell_supervisor_protocol_previous_sessions_total").increment(1); - warn!( - sandbox_id = %sandbox_id, - "supervisor session: Stage 1 supervisor is using polling compatibility" - ); - Ok(()) - } - LEGACY_SUPERVISOR_PROTOCOL_REVISION => { - counter!("openshell_supervisor_protocol_legacy_sessions_total").increment(1); - warn!( - sandbox_id = %sandbox_id, - "supervisor session: supervisor predates the protocol handshake; recreate the sandbox before the next gateway upgrade" - ); - Ok(()) - } - other => Err(Status::failed_precondition(format!( - "supervisor protocol revision mismatch: gateway requires {SUPERVISOR_PROTOCOL_REVISION}, supervisor offered {other}" - ))), +fn validate_protocol_revision(_sandbox_id: &str, supervisor_revision: u32) -> Result<(), Status> { + if supervisor_revision == SUPERVISOR_PROTOCOL_REVISION { + Ok(()) + } else { + Err(Status::failed_precondition(format!( + "supervisor protocol revision mismatch: gateway requires {SUPERVISOR_PROTOCOL_REVISION}, supervisor offered {supervisor_revision}" + ))) } } @@ -1345,7 +1306,6 @@ async fn run_session_loop( sandbox_id: &str, session_id: &str, instance_id: &str, - stream_applies_config: bool, expected_bootstrap_revisions: &[(ConfigComponent, ConfigSnapshotRevision)], tx: &mpsc::Sender, inbound: &mut tonic::Streaming, @@ -1357,7 +1317,7 @@ async fn run_session_loop( heartbeat_timer.tick().await; let bootstrap_timeout = tokio::time::sleep(Duration::from_mins(2)); tokio::pin!(bootstrap_timeout); - let mut bootstrap_complete = !stream_applies_config; + let mut bootstrap_complete = false; loop { tokio::select! { @@ -1369,8 +1329,7 @@ async fn run_session_loop( match msg { Ok(Some(msg)) => { let bootstrap_succeeded = match msg.payload.as_ref() { - Some(supervisor_message::Payload::ConfigBootstrapResult(result)) - if stream_applies_config => + Some(supervisor_message::Payload::ConfigBootstrapResult(result)) => { match validate_bootstrap_result( &result.results, @@ -1394,7 +1353,6 @@ async fn run_session_loop( state, sandbox_id, session_id, - stream_applies_config, msg, ).await; match bootstrap_succeeded { @@ -1467,7 +1425,6 @@ async fn handle_supervisor_message( state: &Arc, sandbox_id: &str, session_id: &str, - stream_applies_config: bool, msg: SupervisorMessage, ) { match msg.payload { @@ -1518,41 +1475,47 @@ async fn handle_supervisor_message( ); return; } - if let Some(result) = result.result.as_ref() - && let Err(error) = record_component_apply_result(state, sandbox_id, result).await - { - state - .supervisor_sessions - .retry_config_update_after_persistence_failure(sandbox_id, session_id, result); - warn!( - sandbox_id, - session_id, - component = result.component, - error = %error, - "failed to persist supervisor configuration result" - ); + if let Some(result) = result.result.as_ref() { + let persisted = record_component_apply_result(state, sandbox_id, result).await; + let completed = if persisted.is_ok() { + crate::config_update_operation::complete_from_apply_result( + state, sandbox_id, result, + ) + .await + } else { + Ok(()) + }; + if let Err(error) = persisted.and(completed) { + state + .supervisor_sessions + .retry_config_update_after_persistence_failure( + sandbox_id, session_id, result, + ); + warn!( + sandbox_id, + session_id, + component = result.component, + error = %error, + "failed to persist supervisor configuration result" + ); + } } } Some(supervisor_message::Payload::ConfigBootstrapResult(result)) => { - if !stream_applies_config { - debug!( - sandbox_id, - session_id, "ignored bootstrap result from polling-compatibility supervisor" - ); - return; - } if !state .supervisor_sessions .is_current_session(sandbox_id, session_id) { return; } + let mut persisted_results = Vec::with_capacity(result.results.len()); for component in &result.results { match record_component_apply_result(state, sandbox_id, component).await { Ok(()) => { state .supervisor_sessions .acknowledge_bootstrap_component(sandbox_id, session_id, component); + persisted_results.push(component.clone()); } Err(error) => { warn!( @@ -1565,6 +1528,23 @@ async fn handle_supervisor_message( } } } + if let Err(error) = crate::config_update_operation::complete_from_apply_results( + state, + sandbox_id, + &persisted_results, + ) + .await + { + for component in &persisted_results { + warn!( + sandbox_id, + session_id, + component = component.component, + error = %error, + "failed to complete operation from supervisor bootstrap result" + ); + } + } } _ => { warn!( @@ -1590,6 +1570,7 @@ fn bootstrap_revision_fence( policy_version: snapshot.version, policy_source: snapshot.policy_source, global_policy_version: snapshot.global_policy_version, + settings_revision: snapshot.settings_revision, }, )), }, @@ -1873,10 +1854,9 @@ mod tests { } #[test] - fn supervisor_protocol_revision_accepts_current_and_legacy_peers() { + fn supervisor_protocol_revision_accepts_only_current_peer() { assert!(validate_protocol_revision("sb-1", SUPERVISOR_PROTOCOL_REVISION).is_ok()); - assert!(validate_protocol_revision("sb-1", PREVIOUS_SUPERVISOR_PROTOCOL_REVISION).is_ok()); - assert!(validate_protocol_revision("sb-1", LEGACY_SUPERVISOR_PROTOCOL_REVISION).is_ok()); + assert!(validate_protocol_revision("sb-1", SUPERVISOR_PROTOCOL_REVISION - 1).is_err()); } #[test] @@ -1968,7 +1948,6 @@ mod tests { &state, "sb-bootstrap-ack", "session-1", - true, SupervisorMessage { payload: Some(supervisor_message::Payload::ConfigBootstrapResult( ConfigBootstrapResult { @@ -2044,6 +2023,7 @@ mod tests { assert!(observation.observed_at_ms > 0); } + #[allow(dead_code)] async fn first_gateway_message( harness: &mut crate::grpc::test_support::SupervisorStreamHarness, ) -> GatewayMessage { @@ -2054,60 +2034,6 @@ mod tests { .expect("gateway message") } - #[tokio::test] - async fn legacy_supervisor_without_protocol_revision_is_accepted() { - let state = state_with_sandbox("sb-legacy").await; - let mut harness = crate::grpc::test_support::connect_supervisor_stream( - &state, - "sb-legacy", - LEGACY_SUPERVISOR_PROTOCOL_REVISION, - ) - .await - .expect("legacy supervisor must connect"); - - let Some(gateway_message::Payload::SessionAccepted(accepted)) = - first_gateway_message(&mut harness).await.payload - else { - panic!("expected SessionAccepted"); - }; - assert_eq!( - accepted.protocol_revision, - LEGACY_SUPERVISOR_PROTOCOL_REVISION - ); - assert!( - state - .supervisor_sessions - .is_current_session("sb-legacy", &accepted.session_id) - ); - } - - #[tokio::test] - async fn stage_one_supervisor_uses_polling_compatibility() { - let state = state_with_sandbox("sb-stage-one").await; - let mut harness = crate::grpc::test_support::connect_supervisor_stream( - &state, - "sb-stage-one", - PREVIOUS_SUPERVISOR_PROTOCOL_REVISION, - ) - .await - .expect("Stage 1 supervisor must connect"); - - let Some(gateway_message::Payload::SessionAccepted(accepted)) = - first_gateway_message(&mut harness).await.payload - else { - panic!("expected SessionAccepted"); - }; - assert_eq!( - accepted.protocol_revision, - PREVIOUS_SUPERVISOR_PROTOCOL_REVISION - ); - assert!( - state - .supervisor_sessions - .is_current_session("sb-stage-one", &accepted.session_id) - ); - } - #[tokio::test] async fn unknown_supervisor_protocol_revision_is_rejected() { let state = state_with_sandbox("sb-future").await; diff --git a/crates/openshell-server/tests/common/mod.rs b/crates/openshell-server/tests/common/mod.rs index 695d8d8f2a..45563430cd 100644 --- a/crates/openshell-server/tests/common/mod.rs +++ b/crates/openshell-server/tests/common/mod.rs @@ -19,8 +19,7 @@ use openshell_core::proto::{ ExchangeProviderSubjectTokenRequest, ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, GatewayMessage, GetGatewayConfigRequest, GetGatewayConfigResponse, GetProviderRequest, GetSandboxConfigRequest, - GetSandboxConfigResponse, GetSandboxProviderEnvironmentRequest, - GetSandboxProviderEnvironmentResponse, GetSandboxRequest, HealthRequest, HealthResponse, + GetSandboxConfigResponse, GetSandboxRequest, HealthRequest, HealthResponse, IssueSandboxTokenRequest, IssueSandboxTokenResponse, ListProvidersRequest, ListProvidersResponse, ListSandboxesRequest, ListSandboxesResponse, ProviderResponse, RefreshSandboxTokenRequest, RefreshSandboxTokenResponse, RelayFrame, RevokeSshSessionRequest, @@ -209,15 +208,6 @@ impl OpenShell for TestOpenShell { Ok(Response::new(GetGatewayConfigResponse::default())) } - async fn get_sandbox_provider_environment( - &self, - _request: tonic::Request, - ) -> Result, Status> { - Ok(Response::new( - GetSandboxProviderEnvironmentResponse::default(), - )) - } - async fn create_ssh_session( &self, _request: tonic::Request, @@ -414,6 +404,17 @@ impl OpenShell for TestOpenShell { Ok(Response::new(ReceiverStream::new(rx))) } + #[allow(unused_qualifications)] + async fn get_config_update_operation( + &self, + _request: tonic::Request, + ) -> Result< + tonic::Response, + tonic::Status, + > { + Err(tonic::Status::unimplemented("unused")) + } + async fn update_config( &self, _request: tonic::Request, diff --git a/crates/openshell-server/tests/supervisor_relay_integration.rs b/crates/openshell-server/tests/supervisor_relay_integration.rs index 448ae2cc7b..a84ff1332c 100644 --- a/crates/openshell-server/tests/supervisor_relay_integration.rs +++ b/crates/openshell-server/tests/supervisor_relay_integration.rs @@ -239,13 +239,6 @@ impl OpenShell for RelayGateway { ) -> Result, Status> { Err(Status::unimplemented("unused")) } - async fn get_sandbox_provider_environment( - &self, - _: tonic::Request, - ) -> Result, Status> - { - Err(Status::unimplemented("unused")) - } async fn create_ssh_session( &self, _: tonic::Request, @@ -391,6 +384,17 @@ impl OpenShell for RelayGateway { ) -> Result, Status> { Err(Status::unimplemented("unused")) } + #[allow(unused_qualifications)] + async fn get_config_update_operation( + &self, + _request: tonic::Request, + ) -> Result< + tonic::Response, + tonic::Status, + > { + Err(tonic::Status::unimplemented("unused")) + } + async fn update_config( &self, _: tonic::Request, diff --git a/crates/openshell-supervisor-network/src/policy_local.rs b/crates/openshell-supervisor-network/src/policy_local.rs index 76d2b87c84..05554eae04 100644 --- a/crates/openshell-supervisor-network/src/policy_local.rs +++ b/crates/openshell-supervisor-network/src/policy_local.rs @@ -884,7 +884,7 @@ fn is_terminal_status(status: &str) -> bool { /// The polling cadence here is faster than `PROPOSAL_WAIT_POLL_INTERVAL` /// (which paces upstream gateway calls). This loop only reads in-memory /// state, so 200ms gives a responsive handoff to the agent's retry once -/// the supervisor's own policy poll catches up. +/// the supervisor's stream-delivered desired state catches up. async fn wait_for_local_policy_to_cover( ctx: &PolicyLocalContext, proposed_rule: &NetworkPolicyRule, diff --git a/crates/openshell-supervisor-network/src/run.rs b/crates/openshell-supervisor-network/src/run.rs index af9ffb8407..35ae5661e0 100644 --- a/crates/openshell-supervisor-network/src/run.rs +++ b/crates/openshell-supervisor-network/src/run.rs @@ -151,7 +151,7 @@ pub struct Networking { pub proxy: Option, pub ca_file_paths: Option<(std::path::PathBuf, std::path::PathBuf)>, - /// Policy-local route context: shared with the orchestrator's policy poll + /// Policy-local route context: shared with the orchestrator's stream configuration loop /// loop so it can publish updated `SandboxPolicy` snapshots that the /// `policy.local` route handler returns to the workload. pub policy_local_ctx: Arc, @@ -197,7 +197,7 @@ pub async fn run_networking( upstream_proxy_args: &crate::upstream_proxy::UpstreamProxyArgs, #[cfg(target_os = "linux")] transparent_runtime: Option, ) -> Result { - // Build the policy-local route context. The orchestrator's policy poll + // Build the policy-local route context. The orchestrator's stream configuration loop // loop also holds an `Arc` clone (via `Networking::policy_local_ctx`) so // it can publish updated policy snapshots after a successful reload. let policy_local_ctx = Arc::new(PolicyLocalContext::new( diff --git a/crates/openshell-supervisor-process/src/run.rs b/crates/openshell-supervisor-process/src/run.rs index 936e46b2b9..4723a11dbf 100644 --- a/crates/openshell-supervisor-process/src/run.rs +++ b/crates/openshell-supervisor-process/src/run.rs @@ -6,7 +6,7 @@ //! Spawns the SSH server, optional supervisor session, the entrypoint child //! process, and waits for it to exit (with optional timeout). Long-running //! background tasks that aren't strictly tied to the workload's lifetime -//! (policy poll loop, denial aggregator, symlink resolver) live in the +//! (stream configuration loop, denial aggregator, symlink resolver) live in the //! orchestrator, not here. use miette::{IntoDiagnostic, Result}; @@ -125,17 +125,9 @@ pub async fn run_process( )?; } - // Eagerly fetch initial settings and install the agent skill if the - // proposals flag is on at startup, rather than waiting for the policy - // poll loop's first tick. In offline/file-mode there is no gateway, so - // the flag stays at its default (false) and no skill is installed. - install_initial_agent_skill( - sandbox_id, - openshell_endpoint, - &agent_proposals, - prepared_supervisor_session.is_none(), - ) - .await; + // Stream bootstrap has already initialized the proposal flag in online + // mode. Offline/file mode retains the local default. + install_initial_agent_skill(&agent_proposals); // Provider token grants may mount supervisor-only identity sockets such as // the SPIFFE Workload API. Prepare the child mount namespace that hides @@ -733,42 +725,9 @@ fn ssh_proxy_url_for_policy( proxy.http_addr.map(|addr| format!("http://{addr}")) } -/// Eagerly fetch initial settings and install the agent-driven policy -/// proposal skill if the flag is on at startup. -/// -/// Without this, the skill would only get installed on the policy poll -/// loop's first false→true transition, which can be ~10 s after launch — -/// long enough for an agent to start running without seeing it. -/// -/// Best-effort: any failure (no gateway, RPC error, install failure) is -/// logged but does not fail sandbox startup. -async fn install_initial_agent_skill( - sandbox_id: Option<&str>, - openshell_endpoint: Option<&str>, - agent_proposals: &AgentProposals, - fetch_settings: bool, -) { - use openshell_core::proto::setting_value; - - if fetch_settings - && let (Some(id), Some(endpoint)) = (sandbox_id, openshell_endpoint) - && let Ok(client) = - openshell_core::grpc_client::CachedOpenShellClient::connect(endpoint).await - && let Ok(result) = client.poll_settings(id).await - { - let initial = result - .settings - .get(openshell_core::settings::AGENT_POLICY_PROPOSALS_ENABLED_KEY) - .and_then(|es| es.value.as_ref()) - .and_then(|sv| sv.value.as_ref()) - .and_then(|v| match v { - setting_value::Value::BoolValue(b) => Some(*b), - _ => None, - }) - .unwrap_or(false); - agent_proposals.set_enabled(initial); - } - +/// Install the agent-driven policy proposal skill when enabled by the stream +/// bootstrap. Best-effort installation does not fail sandbox startup. +fn install_initial_agent_skill(agent_proposals: &AgentProposals) { if agent_proposals.enabled() { match crate::skills::install_static_skills() { Ok(installed) => info!( diff --git a/crates/openshell-supervisor-process/src/supervisor_session.rs b/crates/openshell-supervisor-process/src/supervisor_session.rs index c1892eca2c..c5dd43e56e 100644 --- a/crates/openshell-supervisor-process/src/supervisor_session.rs +++ b/crates/openshell-supervisor-process/src/supervisor_session.rs @@ -17,6 +17,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; +use openshell_core::proto::SUPERVISOR_PROTOCOL_REVISION; use openshell_core::proto::open_shell_client::OpenShellClient; use openshell_core::proto::{ ConfigApplyFailure, ConfigApplyOutcome, ConfigBootstrap, ConfigBootstrapResult, @@ -26,10 +27,6 @@ use openshell_core::proto::{ SupervisorMessage, TcpRelayTarget, config_snapshot_revision, config_update, gateway_message, relay_open, supervisor_message, }; -use openshell_core::proto::{ - LEGACY_SUPERVISOR_PROTOCOL_REVISION, PREVIOUS_SUPERVISOR_PROTOCOL_REVISION, - SUPERVISOR_PROTOCOL_REVISION, -}; use openshell_ocsf::{ ActivityId, ConnectionInfo, Endpoint, EventContext, NetworkActivityBuilder, OcsfEvent, SeverityId, StatusId, ocsf_emit, @@ -127,6 +124,7 @@ fn update_component_and_revision( policy_version: snapshot.version, policy_source: snapshot.policy_source, global_policy_version: snapshot.global_policy_version, + settings_revision: snapshot.settings_revision, }, )), }), @@ -654,24 +652,12 @@ async fn run_prepared_session( fn validate_gateway_protocol_revision( gateway_revision: u32, ) -> Result<(), Box> { - match gateway_revision { - SUPERVISOR_PROTOCOL_REVISION => Ok(()), - PREVIOUS_SUPERVISOR_PROTOCOL_REVISION => { - warn!( - "supervisor session: gateway uses Stage 1 stream semantics; polling remains active" - ); - Ok(()) - } - LEGACY_SUPERVISOR_PROTOCOL_REVISION => { - warn!( - "supervisor session: gateway predates the protocol handshake; upgrade the gateway before pinning newer supervisor images" - ); - Ok(()) - } - other => Err(format!( - "supervisor protocol revision mismatch: supervisor requires {SUPERVISOR_PROTOCOL_REVISION}, gateway offered {other}" - ) - .into()), + if gateway_revision == SUPERVISOR_PROTOCOL_REVISION { + Ok(()) + } else { + Err(format!( + "supervisor protocol revision mismatch: supervisor requires {SUPERVISOR_PROTOCOL_REVISION}, gateway offered {gateway_revision}" + ).into()) } } @@ -730,6 +716,7 @@ fn bootstrap_components( policy_version: snapshot.version, policy_source: snapshot.policy_source, global_policy_version: snapshot.global_policy_version, + settings_revision: snapshot.settings_revision, }, )), }), @@ -1240,10 +1227,9 @@ mod target_tests { use super::*; #[test] - fn gateway_protocol_revision_accepts_current_and_legacy_peers() { + fn gateway_protocol_revision_accepts_only_current_peer() { assert!(validate_gateway_protocol_revision(SUPERVISOR_PROTOCOL_REVISION).is_ok()); - assert!(validate_gateway_protocol_revision(PREVIOUS_SUPERVISOR_PROTOCOL_REVISION).is_ok()); - assert!(validate_gateway_protocol_revision(LEGACY_SUPERVISOR_PROTOCOL_REVISION).is_ok()); + assert!(validate_gateway_protocol_revision(SUPERVISOR_PROTOCOL_REVISION - 1).is_err()); } #[test] diff --git a/docs/get-started/tutorials/microsoft-graph-provider-refresh.mdx b/docs/get-started/tutorials/microsoft-graph-provider-refresh.mdx index 76adf55fde..9e1c3fb501 100644 --- a/docs/get-started/tutorials/microsoft-graph-provider-refresh.mdx +++ b/docs/get-started/tutorials/microsoft-graph-provider-refresh.mdx @@ -173,7 +173,7 @@ The request uses the [Microsoft Graph list messages API](https://learn.microsoft ## Update Running Sandboxes -Provider refresh updates the provider record at the gateway. Running sandboxes poll for provider environment revisions, but already-running processes keep the environment they started with. +Provider refresh updates the provider record at the gateway. Running sandboxes receive provider environment revisions over the supervisor configuration stream, but already-running processes keep the environment they started with. If you attach this provider to an existing sandbox or update provider credentials after a process has already started, launch a new process inside the sandbox before expecting `MS_GRAPH_ACCESS_TOKEN` to appear in that process environment. diff --git a/docs/kubernetes/topology.mdx b/docs/kubernetes/topology.mdx index 869fc07f1b..3d85b48bf7 100644 --- a/docs/kubernetes/topology.mdx +++ b/docs/kubernetes/topology.mdx @@ -171,7 +171,7 @@ the workload, the process supervisor establishes the only accepted connection. The sidecar validates its UID, GID, and PID with peer credentials, unlinks the listener, derives the SSH target from trusted configuration, and rejects later clients. The connection receives bootstrap state and provider-environment -updates after settings polls. If it closes, the network sidecar exits so +updates from gateway stream snapshots. If it closes, the network sidecar exits so Kubernetes recreates the one-client bootstrap listener, and the process supervisor exits so Kubernetes terminates the workload and restarts the agent container. This symmetric failure behavior prevents a surviving workload from diff --git a/docs/observability/logging.mdx b/docs/observability/logging.mdx index 4bc4aad6de..77a0f0c611 100644 --- a/docs/observability/logging.mdx +++ b/docs/observability/logging.mdx @@ -171,10 +171,9 @@ A process launched inside the sandbox: OCSF PROC:LAUNCH [INFO] sleep(49) ``` -A policy reload after a settings change: +A policy reload after a stream-delivered configuration change: ```text -OCSF CONFIG:DETECTED [INFO] Settings poll: config change detected [old_revision:2915564174587774909 new_revision:11008534403127604466 policy_changed:true] OCSF CONFIG:LOADED [INFO] Policy reloaded successfully [policy_hash:0cc0c2b525573c07] ``` diff --git a/docs/providers/profiles.mdx b/docs/providers/profiles.mdx index 79ccd78bd7..850ced6624 100644 --- a/docs/providers/profiles.mdx +++ b/docs/providers/profiles.mdx @@ -978,7 +978,7 @@ Attach and detach are idempotent. Attach validates that the provider exists befo ### Runtime Limitations -Provider attach and detach update the persisted sandbox provider list. Running sandboxes poll for provider environment revisions and effective policy changes. +Provider attach and detach update the persisted sandbox provider list. Running sandboxes receive provider environment and effective policy revisions over the supervisor configuration stream. The policy effect applies to future effective policy reads after the sandbox observes the update. The credential environment effect applies only to new process launches after the update is observed, such as later SSH, exec, or SFTP sessions. diff --git a/docs/reference/gateway-config.mdx b/docs/reference/gateway-config.mdx index a5af706234..0fea524c1c 100644 --- a/docs/reference/gateway-config.mdx +++ b/docs/reference/gateway-config.mdx @@ -22,7 +22,7 @@ Gateway CLI flag > gateway OPENSHELL_* env var > TOML file > built-in defa ## Supervisor connections and policy recovery -Current supervisors open `ConnectSupervisor` before initializing gateway-owned runtime state and require a complete configuration bootstrap before the gateway accepts the session as initialized. They apply later policy, settings, middleware, and provider snapshots from that stream without configuration fetch polling. If first-start image policy discovery or image-specific baseline enrichment changes policy, the supervisor commits that repair and reconnects before installing the fresh stream bootstrap; it does not initialize from the mutation response. The gateway gives bootstrap construction the same bounded 45-second window as other snapshot builds and rejects the connection when construction fails or times out. Immediately previous supervisors retain the Stage 1 compatibility behavior: the gateway waits at most one second for an optional bootstrap, accepts the session without it when necessary, and relies on supervisor polling for configuration. +Current supervisors open `ConnectSupervisor` before initializing gateway-owned runtime state and require a complete configuration bootstrap before the gateway accepts the session as initialized. They apply later policy, settings, middleware, and provider snapshots from that stream without configuration fetch polling. If first-start image policy discovery or image-specific baseline enrichment changes policy, the supervisor commits that repair and reconnects before installing the fresh stream bootstrap; it does not initialize from the mutation response. The gateway gives bootstrap construction the same bounded 45-second window as other snapshot builds and rejects the connection when construction fails or times out. Gateway and supervisor protocol revisions must match exactly; deployments upgrade those components together rather than falling back to polling for older peers. On startup, the gateway repairs missing legacy policy history and skips invalid stored policies. A valid global policy can still override an invalid local policy; otherwise, that sandbox's configuration reads continue to report the validation failure. @@ -156,6 +156,19 @@ enable_loopback_service_http = true # Set true only for local plaintext gateways or trusted TLS termination. disable_tls = false +# Shared driver defaults. These inherit into [openshell.drivers.] tables +# when the driver-specific table does not override them. +default_image = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest" +# Defaults to the gateway version. The gateway and supervisor use a +# release-matched internal protocol revision; every other revision rejects the +# session so configuration is never applied through a removed fallback path. +# supervisor_image = "ghcr.io/nvidia/openshell/supervisor:" +client_tls_secret_name = "openshell-client-tls" +service_account_name = "openshell-sandbox" +host_gateway_ip = "10.0.0.1" +enable_user_namespaces = false +sa_token_ttl_secs = 3600 + # Guest TLS paths remain gateway settings. TLS-enabled Docker, Podman, and VM # gateways require a complete bundle unless package-managed local TLS supplies # it automatically. Omit all three when TLS is disabled. Kubernetes projects diff --git a/docs/reference/sandbox-compute-drivers.mdx b/docs/reference/sandbox-compute-drivers.mdx index f328909ceb..a1339d606b 100644 --- a/docs/reference/sandbox-compute-drivers.mdx +++ b/docs/reference/sandbox-compute-drivers.mdx @@ -517,7 +517,7 @@ network sidecar owns gateway authentication and writes local policy/provider state to the process supervisor over a local control socket, so the agent container does not mount the sandbox bootstrap token or client TLS secret in the default sidecar path. The provider environment is refreshed by the network -sidecar after settings polls and streamed to the process supervisor so future +sidecar from gateway stream snapshots and forwarded to the process supervisor so future child processes can see updated provider env without gateway access in the agent container. Sidecar mode keeps gateway session and SSH behavior. The process supervisor diff --git a/docs/sandboxes/inference-routing.mdx b/docs/sandboxes/inference-routing.mdx index 902c09eea0..93533af79a 100644 --- a/docs/sandboxes/inference-routing.mdx +++ b/docs/sandboxes/inference-routing.mdx @@ -101,7 +101,8 @@ openshell sandbox provider attach inference-demo nvidia-prod openshell sandbox provider list inference-demo ``` -Running sandboxes poll for provider and effective-policy changes. Launch a new +Running sandboxes receive provider and effective-policy changes over the +supervisor configuration stream. Launch a new process after attachment so it receives the new credential placeholder: ```shell diff --git a/docs/sandboxes/policies.mdx b/docs/sandboxes/policies.mdx index da2cb16eb4..8f48d9a29b 100644 --- a/docs/sandboxes/policies.mdx +++ b/docs/sandboxes/policies.mdx @@ -326,10 +326,12 @@ The incremental update surface is split into endpoint-level operations and metho | `--binary ` | Adds binaries to every `--add-endpoint` rule in the same command. | Bind a new endpoint to one or more executables. | | `--rule-name ` | Overrides the generated rule name. | Keep a stable human-chosen rule name when adding exactly one endpoint. | | `--dry-run` | Shows the merged policy locally and does not call the gateway. | Review the result before persisting it. | -| `--wait` | Polls until the sandbox reports that the new revision loaded. | Confirm the change took effect before continuing. | -| `--timeout ` | Sets the timeout for `--wait`. | Extend the wait window for slower sandboxes. | +| `--wait` | Asks the gateway to wait on the durable update operation until the exact revision is applied or reaches another terminal state. | Confirm the change took effect before continuing. | +| `--timeout ` | Sets the server-side timeout for `--wait`. A timeout does not roll back the committed update. | Extend the wait window for slower sandboxes. | `--wait` and `--dry-run` cannot be used together. +If the wait times out, the error includes the durable operation ID so a client +can query its later result without resubmitting the mutation. ### Add Endpoint Compared to Allow and Deny diff --git a/docs/sandboxes/policy-advisor.mdx b/docs/sandboxes/policy-advisor.mdx index 80884620d5..d494ae1c87 100644 --- a/docs/sandboxes/policy-advisor.mdx +++ b/docs/sandboxes/policy-advisor.mdx @@ -45,7 +45,7 @@ openshell settings delete --global \ --yes ``` -Set the value before creating a sandbox when you want the first denied request to include policy advisor guidance. Running sandboxes poll settings and can enable the surface after startup, but startup enablement gives the agent the clearest first-denial path. +Set the value before creating a sandbox when you want the first denied request to include policy advisor guidance. Running sandboxes receive setting updates over the supervisor configuration stream and can enable the surface after startup, but startup enablement gives the agent the clearest first-denial path. ## Approval Modes diff --git a/e2e/rust/tests/live_policy_update.rs b/e2e/rust/tests/live_policy_update.rs index 7618696c62..c732167aa3 100644 --- a/e2e/rust/tests/live_policy_update.rs +++ b/e2e/rust/tests/live_policy_update.rs @@ -12,7 +12,7 @@ //! //! These tests replace the Python e2e tests `test_live_policy_update_and_logs` //! and `test_live_policy_update_from_empty_network_policies`, which were flaky -//! due to hard-coded 90s poll timeouts. The Rust tests use the CLI's built-in +//! due to hard-coded 90s wait timeouts. The Rust tests use the CLI's built-in //! `--wait` flag for reliable synchronization. //! //! Note: the removed Python tests also covered `GetSandboxLogs` RPC and @@ -595,11 +595,11 @@ async fn initial_sparse_policy_is_acknowledged_as_loaded() { /// An explicit local Rego/data override remains authoritative even when the /// sandbox has a gateway policy and that policy changes while it is running. -/// Gateway polling must continue for settings and providers without replacing +/// Gateway stream updates must continue for settings and providers without replacing /// the locally loaded OPA engine. #[cfg(feature = "e2e-docker")] #[tokio::test] -async fn local_policy_override_survives_gateway_policy_polls() { +async fn local_policy_override_survives_gateway_stream_updates() { let (_image_context, image) = write_local_override_image().expect("write local override image"); let gateway_policy_a_file = write_policy(&["example.com"]).expect("write gateway policy A"); @@ -632,8 +632,8 @@ async fn local_policy_override_survives_gateway_policy_polls() { .await .expect("create sandbox with local policy override"); - // Allow several one-second poll intervals. Before the fix, the first poll - // immediately reloaded gateway policy A over the local override. + // Allow enough time for initial stream reconciliation. Before the fix, the + // first gateway snapshot reloaded policy A over the local override. tokio::time::sleep(std::time::Duration::from_secs(4)).await; let initial_logs = run_cli(&[ "logs", @@ -660,7 +660,7 @@ async fn local_policy_override_survives_gateway_policy_polls() { ); assert!( !initial_logs.output.contains("Policy reloaded successfully"), - "the first gateway poll must not replace the local policy:\n{}", + "the first gateway stream snapshot must not replace the local policy:\n{}", initial_logs.output ); diff --git a/proto/openshell.proto b/proto/openshell.proto index 17f6f082ef..0f0354681d 100644 --- a/proto/openshell.proto +++ b/proto/openshell.proto @@ -447,6 +447,16 @@ service OpenShell { }; } + // Get a durable sandbox configuration update operation by id. + rpc GetConfigUpdateOperation(GetConfigUpdateOperationRequest) + returns (GetConfigUpdateOperationResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:read" + workspace_role: "user" + }; + } + // Get the load status of a specific policy version. rpc GetSandboxPolicyStatus(GetSandboxPolicyStatusRequest) returns (GetSandboxPolicyStatusResponse) { @@ -475,14 +485,6 @@ service OpenShell { }; } - // Get provider environment for a sandbox (called by sandbox supervisor at startup). - rpc GetSandboxProviderEnvironment(GetSandboxProviderEnvironmentRequest) - returns (GetSandboxProviderEnvironmentResponse) { - option (openshell.options.v1.authorization) = { - auth_mode: "sandbox" - }; - } - // Exchange a stored provider subject token for an intermediate token scoped // to the calling supervisor's SPIFFE identity. rpc ExchangeProviderSubjectToken(ExchangeProviderSubjectTokenRequest) @@ -2273,6 +2275,55 @@ message UpdateConfigRequest { // Explicit workspace scope for sandbox-scoped updates. Omit only when // `global` is true; the all-workspaces selection is invalid. openshell.datamodel.v1.WorkspaceSelector workspace_scope = 11; + // Controls whether the RPC returns after commit or after the runtime reaches + // a terminal apply result. Unspecified preserves commit-only compatibility. + ConfigUpdateConsistency consistency = 12; + // Optional retry key scoped to the sandbox. Reusing a key returns the + // original operation and never creates another desired-state revision. + string idempotency_key = 13; + // Server-side wait bound for WAIT_FOR_APPLY. Zero uses 60 seconds. The + // operation remains durable and may complete after this RPC times out. + uint32 wait_timeout_secs = 14; +} + +enum ConfigUpdateConsistency { + CONFIG_UPDATE_CONSISTENCY_UNSPECIFIED = 0; + CONFIG_UPDATE_CONSISTENCY_COMMIT_ONLY = 1; + CONFIG_UPDATE_CONSISTENCY_WAIT_FOR_APPLY = 2; +} + +enum ConfigUpdateOperationState { + CONFIG_UPDATE_OPERATION_STATE_UNSPECIFIED = 0; + CONFIG_UPDATE_OPERATION_STATE_PENDING = 1; + CONFIG_UPDATE_OPERATION_STATE_APPLIED = 2; + CONFIG_UPDATE_OPERATION_STATE_INACTIVE = 3; + CONFIG_UPDATE_OPERATION_STATE_FAILED = 4; + CONFIG_UPDATE_OPERATION_STATE_SUPERSEDED = 5; + CONFIG_UPDATE_OPERATION_STATE_CANCELLED = 6; +} + +// Durable progress for one sandbox-scoped desired-state mutation. Snapshot +// contents and credentials are never stored in this resource. +message ConfigUpdateOperation { + string operation_id = 1; + string sandbox_id = 2; + ConfigComponent component = 3; + ConfigSnapshotRevision target_revision = 4; + ConfigUpdateOperationState state = 5; + ConfigApplyOutcome outcome = 6; + string sanitized_error = 7; + int64 created_at_ms = 8; + int64 updated_at_ms = 9; + int64 completed_at_ms = 10; +} + +message GetConfigUpdateOperationRequest { + string operation_id = 1; + openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2; +} + +message GetConfigUpdateOperationResponse { + ConfigUpdateOperation operation = 1; } message PolicyMergeOperation { @@ -2330,6 +2381,9 @@ message UpdateConfigResponse { bool deleted = 4; // Sandbox metadata annotations after the update. Empty for global updates. map annotations = 5; + // Durable apply operation for a changed sandbox-scoped mutation. Omitted + // for global changes and idempotent no-op updates. + ConfigUpdateOperation operation = 6; } // Get sandbox policy status request. @@ -2576,6 +2630,9 @@ message SandboxConfigRevision { uint32 policy_version = 2; openshell.sandbox.v1.PolicySource policy_source = 3; uint32 global_policy_version = 4; + // Monotonic revision of the sandbox-scoped settings row. This disambiguates + // setting operations whose effective config fingerprint is equality-only. + uint64 settings_revision = 5; } enum ConfigApplyOutcome { diff --git a/proto/sandbox.proto b/proto/sandbox.proto index 4a08ec335e..c4c0098424 100644 --- a/proto/sandbox.proto +++ b/proto/sandbox.proto @@ -377,6 +377,8 @@ message SandboxConfigSnapshot { string workspace = 10; string policy_validation_failure_mode = 11; bool extension_authentication_enabled = 12; + // Monotonic revision of the sandbox-scoped settings row. + uint64 settings_revision = 13; } // Response containing effective sandbox settings and policy. @@ -414,6 +416,8 @@ message GetSandboxConfigResponse { // False also covers older gateways that do not advertise this capability; // supervisors preserve their legacy unauthenticated connection behavior. bool extension_authentication_enabled = 12; + // Monotonic revision of the sandbox-scoped settings row. + uint64 settings_revision = 13; } // Connection details for one operator-registered supervisor middleware service. diff --git a/sdk/go/openshell/v1/internal/converter/setting.go b/sdk/go/openshell/v1/internal/converter/setting.go index 495545938d..2b13b53720 100644 --- a/sdk/go/openshell/v1/internal/converter/setting.go +++ b/sdk/go/openshell/v1/internal/converter/setting.go @@ -187,6 +187,14 @@ func ConfigUpdateToProto(cu *v1.ConfigUpdate) (*pb.UpdateConfigRequest, error) { Global: cu.Global, ExpectedResourceVersion: cu.ExpectedResourceVersion, Annotations: CopyStringMap(cu.Annotations), + IdempotencyKey: cu.IdempotencyKey, + WaitTimeoutSecs: cu.WaitTimeoutSeconds, + } + switch cu.Consistency { + case v1.ConfigUpdateWaitForApply: + req.Consistency = pb.ConfigUpdateConsistency_CONFIG_UPDATE_CONSISTENCY_WAIT_FOR_APPLY + case v1.ConfigUpdateCommitOnly: + req.Consistency = pb.ConfigUpdateConsistency_CONFIG_UPDATE_CONSISTENCY_COMMIT_ONLY } // Convert typed SDK SandboxPolicy to proto SandboxPolicy. @@ -296,11 +304,21 @@ func ConfigUpdateResultFromProto(resp *pb.UpdateConfigResponse) *v1.ConfigUpdate if resp == nil { return nil } - return &v1.ConfigUpdateResult{ + result := &v1.ConfigUpdateResult{ Version: resp.GetVersion(), PolicyHash: resp.GetPolicyHash(), SettingsRevision: resp.GetSettingsRevision(), Deleted: resp.GetDeleted(), Annotations: CopyStringMap(resp.GetAnnotations()), } + if operation := resp.GetOperation(); operation != nil { + result.Operation = &v1.ConfigUpdateOperation{ + OperationID: operation.GetOperationId(), + SandboxID: operation.GetSandboxId(), + State: operation.GetState().String(), + Outcome: operation.GetOutcome().String(), + SanitizedError: operation.GetSanitizedError(), + } + } + return result } diff --git a/sdk/go/openshell/v1/types/setting.go b/sdk/go/openshell/v1/types/setting.go index 7dd8eff2f2..4affb4d361 100644 --- a/sdk/go/openshell/v1/types/setting.go +++ b/sdk/go/openshell/v1/types/setting.go @@ -104,6 +104,31 @@ type ConfigUpdate struct { ExpectedResourceVersion uint64 // Annotations is caller-provided metadata for sandbox-scoped updates. Annotations map[string]string + // Consistency controls whether Update returns after commit or terminal apply. + Consistency ConfigUpdateConsistency + // IdempotencyKey maps retries to the original durable operation. + IdempotencyKey string + // WaitTimeoutSeconds bounds WaitForApply on the server. Zero uses the server default. + WaitTimeoutSeconds uint32 +} + +// ConfigUpdateConsistency controls configuration mutation response timing. +type ConfigUpdateConsistency string + +const ( + // ConfigUpdateCommitOnly returns after desired state and its operation commit. + ConfigUpdateCommitOnly ConfigUpdateConsistency = "commit_only" + // ConfigUpdateWaitForApply waits for a durable terminal apply result. + ConfigUpdateWaitForApply ConfigUpdateConsistency = "wait_for_apply" +) + +// ConfigUpdateOperation is the durable terminal state for a sandbox update. +type ConfigUpdateOperation struct { + OperationID string + SandboxID string + State string + Outcome string + SanitizedError string } // ConfigUpdateResult holds the result of a configuration update operation. @@ -119,4 +144,6 @@ type ConfigUpdateResult struct { Deleted bool // Annotations contains sandbox metadata annotations after the update. Annotations map[string]string + // Operation is present for changed sandbox-scoped mutations. + Operation *ConfigUpdateOperation } diff --git a/sdk/go/proto/openshellv1/openshell.pb.go b/sdk/go/proto/openshellv1/openshell.pb.go index fc0b2ed1d9..8e7ec937af 100644 --- a/sdk/go/proto/openshellv1/openshell.pb.go +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -330,6 +330,116 @@ func (ProviderEnvironmentValueClassification) EnumDescriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{4} } +type ConfigUpdateConsistency int32 + +const ( + ConfigUpdateConsistency_CONFIG_UPDATE_CONSISTENCY_UNSPECIFIED ConfigUpdateConsistency = 0 + ConfigUpdateConsistency_CONFIG_UPDATE_CONSISTENCY_COMMIT_ONLY ConfigUpdateConsistency = 1 + ConfigUpdateConsistency_CONFIG_UPDATE_CONSISTENCY_WAIT_FOR_APPLY ConfigUpdateConsistency = 2 +) + +// Enum value maps for ConfigUpdateConsistency. +var ( + ConfigUpdateConsistency_name = map[int32]string{ + 0: "CONFIG_UPDATE_CONSISTENCY_UNSPECIFIED", + 1: "CONFIG_UPDATE_CONSISTENCY_COMMIT_ONLY", + 2: "CONFIG_UPDATE_CONSISTENCY_WAIT_FOR_APPLY", + } + ConfigUpdateConsistency_value = map[string]int32{ + "CONFIG_UPDATE_CONSISTENCY_UNSPECIFIED": 0, + "CONFIG_UPDATE_CONSISTENCY_COMMIT_ONLY": 1, + "CONFIG_UPDATE_CONSISTENCY_WAIT_FOR_APPLY": 2, + } +) + +func (x ConfigUpdateConsistency) Enum() *ConfigUpdateConsistency { + p := new(ConfigUpdateConsistency) + *p = x + return p +} + +func (x ConfigUpdateConsistency) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ConfigUpdateConsistency) Descriptor() protoreflect.EnumDescriptor { + return file_openshell_proto_enumTypes[5].Descriptor() +} + +func (ConfigUpdateConsistency) Type() protoreflect.EnumType { + return &file_openshell_proto_enumTypes[5] +} + +func (x ConfigUpdateConsistency) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ConfigUpdateConsistency.Descriptor instead. +func (ConfigUpdateConsistency) EnumDescriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{5} +} + +type ConfigUpdateOperationState int32 + +const ( + ConfigUpdateOperationState_CONFIG_UPDATE_OPERATION_STATE_UNSPECIFIED ConfigUpdateOperationState = 0 + ConfigUpdateOperationState_CONFIG_UPDATE_OPERATION_STATE_PENDING ConfigUpdateOperationState = 1 + ConfigUpdateOperationState_CONFIG_UPDATE_OPERATION_STATE_APPLIED ConfigUpdateOperationState = 2 + ConfigUpdateOperationState_CONFIG_UPDATE_OPERATION_STATE_INACTIVE ConfigUpdateOperationState = 3 + ConfigUpdateOperationState_CONFIG_UPDATE_OPERATION_STATE_FAILED ConfigUpdateOperationState = 4 + ConfigUpdateOperationState_CONFIG_UPDATE_OPERATION_STATE_SUPERSEDED ConfigUpdateOperationState = 5 + ConfigUpdateOperationState_CONFIG_UPDATE_OPERATION_STATE_CANCELLED ConfigUpdateOperationState = 6 +) + +// Enum value maps for ConfigUpdateOperationState. +var ( + ConfigUpdateOperationState_name = map[int32]string{ + 0: "CONFIG_UPDATE_OPERATION_STATE_UNSPECIFIED", + 1: "CONFIG_UPDATE_OPERATION_STATE_PENDING", + 2: "CONFIG_UPDATE_OPERATION_STATE_APPLIED", + 3: "CONFIG_UPDATE_OPERATION_STATE_INACTIVE", + 4: "CONFIG_UPDATE_OPERATION_STATE_FAILED", + 5: "CONFIG_UPDATE_OPERATION_STATE_SUPERSEDED", + 6: "CONFIG_UPDATE_OPERATION_STATE_CANCELLED", + } + ConfigUpdateOperationState_value = map[string]int32{ + "CONFIG_UPDATE_OPERATION_STATE_UNSPECIFIED": 0, + "CONFIG_UPDATE_OPERATION_STATE_PENDING": 1, + "CONFIG_UPDATE_OPERATION_STATE_APPLIED": 2, + "CONFIG_UPDATE_OPERATION_STATE_INACTIVE": 3, + "CONFIG_UPDATE_OPERATION_STATE_FAILED": 4, + "CONFIG_UPDATE_OPERATION_STATE_SUPERSEDED": 5, + "CONFIG_UPDATE_OPERATION_STATE_CANCELLED": 6, + } +) + +func (x ConfigUpdateOperationState) Enum() *ConfigUpdateOperationState { + p := new(ConfigUpdateOperationState) + *p = x + return p +} + +func (x ConfigUpdateOperationState) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ConfigUpdateOperationState) Descriptor() protoreflect.EnumDescriptor { + return file_openshell_proto_enumTypes[6].Descriptor() +} + +func (ConfigUpdateOperationState) Type() protoreflect.EnumType { + return &file_openshell_proto_enumTypes[6] +} + +func (x ConfigUpdateOperationState) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ConfigUpdateOperationState.Descriptor instead. +func (ConfigUpdateOperationState) EnumDescriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{6} +} + // Policy load status. type PolicyStatus int32 @@ -376,11 +486,11 @@ func (x PolicyStatus) String() string { } func (PolicyStatus) Descriptor() protoreflect.EnumDescriptor { - return file_openshell_proto_enumTypes[5].Descriptor() + return file_openshell_proto_enumTypes[7].Descriptor() } func (PolicyStatus) Type() protoreflect.EnumType { - return &file_openshell_proto_enumTypes[5] + return &file_openshell_proto_enumTypes[7] } func (x PolicyStatus) Number() protoreflect.EnumNumber { @@ -389,7 +499,7 @@ func (x PolicyStatus) Number() protoreflect.EnumNumber { // Deprecated: Use PolicyStatus.Descriptor instead. func (PolicyStatus) EnumDescriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{5} + return file_openshell_proto_rawDescGZIP(), []int{7} } type ConfigComponent int32 @@ -425,11 +535,11 @@ func (x ConfigComponent) String() string { } func (ConfigComponent) Descriptor() protoreflect.EnumDescriptor { - return file_openshell_proto_enumTypes[6].Descriptor() + return file_openshell_proto_enumTypes[8].Descriptor() } func (ConfigComponent) Type() protoreflect.EnumType { - return &file_openshell_proto_enumTypes[6] + return &file_openshell_proto_enumTypes[8] } func (x ConfigComponent) Number() protoreflect.EnumNumber { @@ -438,7 +548,7 @@ func (x ConfigComponent) Number() protoreflect.EnumNumber { // Deprecated: Use ConfigComponent.Descriptor instead. func (ConfigComponent) EnumDescriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{6} + return file_openshell_proto_rawDescGZIP(), []int{8} } type ConfigApplyOutcome int32 @@ -492,11 +602,11 @@ func (x ConfigApplyOutcome) String() string { } func (ConfigApplyOutcome) Descriptor() protoreflect.EnumDescriptor { - return file_openshell_proto_enumTypes[7].Descriptor() + return file_openshell_proto_enumTypes[9].Descriptor() } func (ConfigApplyOutcome) Type() protoreflect.EnumType { - return &file_openshell_proto_enumTypes[7] + return &file_openshell_proto_enumTypes[9] } func (x ConfigApplyOutcome) Number() protoreflect.EnumNumber { @@ -505,7 +615,7 @@ func (x ConfigApplyOutcome) Number() protoreflect.EnumNumber { // Deprecated: Use ConfigApplyOutcome.Descriptor instead. func (ConfigApplyOutcome) EnumDescriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{7} + return file_openshell_proto_rawDescGZIP(), []int{9} } // Service status enum. @@ -545,11 +655,11 @@ func (x ServiceStatus) String() string { } func (ServiceStatus) Descriptor() protoreflect.EnumDescriptor { - return file_openshell_proto_enumTypes[8].Descriptor() + return file_openshell_proto_enumTypes[10].Descriptor() } func (ServiceStatus) Type() protoreflect.EnumType { - return &file_openshell_proto_enumTypes[8] + return &file_openshell_proto_enumTypes[10] } func (x ServiceStatus) Number() protoreflect.EnumNumber { @@ -558,7 +668,7 @@ func (x ServiceStatus) Number() protoreflect.EnumNumber { // Deprecated: Use ServiceStatus.Descriptor instead. func (ServiceStatus) EnumDescriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{8} + return file_openshell_proto_rawDescGZIP(), []int{10} } // Workspace-scoped role for members. @@ -595,11 +705,11 @@ func (x WorkspaceRole) String() string { } func (WorkspaceRole) Descriptor() protoreflect.EnumDescriptor { - return file_openshell_proto_enumTypes[9].Descriptor() + return file_openshell_proto_enumTypes[11].Descriptor() } func (WorkspaceRole) Type() protoreflect.EnumType { - return &file_openshell_proto_enumTypes[9] + return &file_openshell_proto_enumTypes[11] } func (x WorkspaceRole) Number() protoreflect.EnumNumber { @@ -608,7 +718,7 @@ func (x WorkspaceRole) Number() protoreflect.EnumNumber { // Deprecated: Use WorkspaceRole.Descriptor instead. func (WorkspaceRole) EnumDescriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{9} + return file_openshell_proto_rawDescGZIP(), []int{11} } // Stable recovery action for the most recent provider credential refresh @@ -654,11 +764,11 @@ func (x ProviderCredentialRefreshRecoveryAction) String() string { } func (ProviderCredentialRefreshRecoveryAction) Descriptor() protoreflect.EnumDescriptor { - return file_openshell_proto_enumTypes[10].Descriptor() + return file_openshell_proto_enumTypes[12].Descriptor() } func (ProviderCredentialRefreshRecoveryAction) Type() protoreflect.EnumType { - return &file_openshell_proto_enumTypes[10] + return &file_openshell_proto_enumTypes[12] } func (x ProviderCredentialRefreshRecoveryAction) Number() protoreflect.EnumNumber { @@ -667,7 +777,7 @@ func (x ProviderCredentialRefreshRecoveryAction) Number() protoreflect.EnumNumbe // Deprecated: Use ProviderCredentialRefreshRecoveryAction.Descriptor instead. func (ProviderCredentialRefreshRecoveryAction) EnumDescriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{10} + return file_openshell_proto_rawDescGZIP(), []int{12} } // IssueSandboxToken request. Empty body; identity is established by the @@ -9288,8 +9398,17 @@ type UpdateConfigRequest struct { // Explicit workspace scope for sandbox-scoped updates. Omit only when // `global` is true; the all-workspaces selection is invalid. WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,11,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Controls whether the RPC returns after commit or after the runtime reaches + // a terminal apply result. Unspecified preserves commit-only compatibility. + Consistency ConfigUpdateConsistency `protobuf:"varint,12,opt,name=consistency,proto3,enum=openshell.v1.ConfigUpdateConsistency" json:"consistency,omitempty"` + // Optional retry key scoped to the sandbox. Reusing a key returns the + // original operation and never creates another desired-state revision. + IdempotencyKey string `protobuf:"bytes,13,opt,name=idempotency_key,json=idempotencyKey,proto3" json:"idempotency_key,omitempty"` + // Server-side wait bound for WAIT_FOR_APPLY. Zero uses 60 seconds. The + // operation remains durable and may complete after this RPC times out. + WaitTimeoutSecs uint32 `protobuf:"varint,14,opt,name=wait_timeout_secs,json=waitTimeoutSecs,proto3" json:"wait_timeout_secs,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UpdateConfigRequest) Reset() { @@ -9392,35 +9511,59 @@ func (x *UpdateConfigRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector return nil } -type PolicyMergeOperation struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Operation: - // - // *PolicyMergeOperation_AddRule - // *PolicyMergeOperation_RemoveEndpoint - // *PolicyMergeOperation_RemoveRule - // *PolicyMergeOperation_AddDenyRules - // *PolicyMergeOperation_AddAllowRules - // *PolicyMergeOperation_RemoveBinary - Operation isPolicyMergeOperation_Operation `protobuf_oneof:"operation"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *UpdateConfigRequest) GetConsistency() ConfigUpdateConsistency { + if x != nil { + return x.Consistency + } + return ConfigUpdateConsistency_CONFIG_UPDATE_CONSISTENCY_UNSPECIFIED } -func (x *PolicyMergeOperation) Reset() { - *x = PolicyMergeOperation{} +func (x *UpdateConfigRequest) GetIdempotencyKey() string { + if x != nil { + return x.IdempotencyKey + } + return "" +} + +func (x *UpdateConfigRequest) GetWaitTimeoutSecs() uint32 { + if x != nil { + return x.WaitTimeoutSecs + } + return 0 +} + +// Durable progress for one sandbox-scoped desired-state mutation. Snapshot +// contents and credentials are never stored in this resource. +type ConfigUpdateOperation struct { + state protoimpl.MessageState `protogen:"open.v1"` + OperationId string `protobuf:"bytes,1,opt,name=operation_id,json=operationId,proto3" json:"operation_id,omitempty"` + SandboxId string `protobuf:"bytes,2,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + Component ConfigComponent `protobuf:"varint,3,opt,name=component,proto3,enum=openshell.v1.ConfigComponent" json:"component,omitempty"` + TargetRevision *ConfigSnapshotRevision `protobuf:"bytes,4,opt,name=target_revision,json=targetRevision,proto3" json:"target_revision,omitempty"` + State ConfigUpdateOperationState `protobuf:"varint,5,opt,name=state,proto3,enum=openshell.v1.ConfigUpdateOperationState" json:"state,omitempty"` + Outcome ConfigApplyOutcome `protobuf:"varint,6,opt,name=outcome,proto3,enum=openshell.v1.ConfigApplyOutcome" json:"outcome,omitempty"` + SanitizedError string `protobuf:"bytes,7,opt,name=sanitized_error,json=sanitizedError,proto3" json:"sanitized_error,omitempty"` + CreatedAtMs int64 `protobuf:"varint,8,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,9,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + CompletedAtMs int64 `protobuf:"varint,10,opt,name=completed_at_ms,json=completedAtMs,proto3" json:"completed_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfigUpdateOperation) Reset() { + *x = ConfigUpdateOperation{} mi := &file_openshell_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *PolicyMergeOperation) String() string { +func (x *ConfigUpdateOperation) String() string { return protoimpl.X.MessageStringOf(x) } -func (*PolicyMergeOperation) ProtoMessage() {} +func (*ConfigUpdateOperation) ProtoMessage() {} -func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { +func (x *ConfigUpdateOperation) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -9432,134 +9575,103 @@ func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use PolicyMergeOperation.ProtoReflect.Descriptor instead. -func (*PolicyMergeOperation) Descriptor() ([]byte, []int) { +// Deprecated: Use ConfigUpdateOperation.ProtoReflect.Descriptor instead. +func (*ConfigUpdateOperation) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{130} } -func (x *PolicyMergeOperation) GetOperation() isPolicyMergeOperation_Operation { +func (x *ConfigUpdateOperation) GetOperationId() string { if x != nil { - return x.Operation + return x.OperationId } - return nil + return "" } -func (x *PolicyMergeOperation) GetAddRule() *AddNetworkRule { +func (x *ConfigUpdateOperation) GetSandboxId() string { if x != nil { - if x, ok := x.Operation.(*PolicyMergeOperation_AddRule); ok { - return x.AddRule - } + return x.SandboxId } - return nil + return "" } -func (x *PolicyMergeOperation) GetRemoveEndpoint() *RemoveNetworkEndpoint { +func (x *ConfigUpdateOperation) GetComponent() ConfigComponent { if x != nil { - if x, ok := x.Operation.(*PolicyMergeOperation_RemoveEndpoint); ok { - return x.RemoveEndpoint - } + return x.Component } - return nil + return ConfigComponent_CONFIG_COMPONENT_UNSPECIFIED } -func (x *PolicyMergeOperation) GetRemoveRule() *RemoveNetworkRule { +func (x *ConfigUpdateOperation) GetTargetRevision() *ConfigSnapshotRevision { if x != nil { - if x, ok := x.Operation.(*PolicyMergeOperation_RemoveRule); ok { - return x.RemoveRule - } + return x.TargetRevision } return nil } -func (x *PolicyMergeOperation) GetAddDenyRules() *AddDenyRules { +func (x *ConfigUpdateOperation) GetState() ConfigUpdateOperationState { if x != nil { - if x, ok := x.Operation.(*PolicyMergeOperation_AddDenyRules); ok { - return x.AddDenyRules - } + return x.State } - return nil + return ConfigUpdateOperationState_CONFIG_UPDATE_OPERATION_STATE_UNSPECIFIED } -func (x *PolicyMergeOperation) GetAddAllowRules() *AddAllowRules { +func (x *ConfigUpdateOperation) GetOutcome() ConfigApplyOutcome { if x != nil { - if x, ok := x.Operation.(*PolicyMergeOperation_AddAllowRules); ok { - return x.AddAllowRules - } + return x.Outcome } - return nil + return ConfigApplyOutcome_CONFIG_APPLY_OUTCOME_UNSPECIFIED } -func (x *PolicyMergeOperation) GetRemoveBinary() *RemoveNetworkBinary { +func (x *ConfigUpdateOperation) GetSanitizedError() string { if x != nil { - if x, ok := x.Operation.(*PolicyMergeOperation_RemoveBinary); ok { - return x.RemoveBinary - } + return x.SanitizedError } - return nil -} - -type isPolicyMergeOperation_Operation interface { - isPolicyMergeOperation_Operation() -} - -type PolicyMergeOperation_AddRule struct { - AddRule *AddNetworkRule `protobuf:"bytes,1,opt,name=add_rule,json=addRule,proto3,oneof"` -} - -type PolicyMergeOperation_RemoveEndpoint struct { - RemoveEndpoint *RemoveNetworkEndpoint `protobuf:"bytes,2,opt,name=remove_endpoint,json=removeEndpoint,proto3,oneof"` -} - -type PolicyMergeOperation_RemoveRule struct { - RemoveRule *RemoveNetworkRule `protobuf:"bytes,3,opt,name=remove_rule,json=removeRule,proto3,oneof"` + return "" } -type PolicyMergeOperation_AddDenyRules struct { - AddDenyRules *AddDenyRules `protobuf:"bytes,4,opt,name=add_deny_rules,json=addDenyRules,proto3,oneof"` +func (x *ConfigUpdateOperation) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 } -type PolicyMergeOperation_AddAllowRules struct { - AddAllowRules *AddAllowRules `protobuf:"bytes,5,opt,name=add_allow_rules,json=addAllowRules,proto3,oneof"` +func (x *ConfigUpdateOperation) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 } -type PolicyMergeOperation_RemoveBinary struct { - RemoveBinary *RemoveNetworkBinary `protobuf:"bytes,6,opt,name=remove_binary,json=removeBinary,proto3,oneof"` +func (x *ConfigUpdateOperation) GetCompletedAtMs() int64 { + if x != nil { + return x.CompletedAtMs + } + return 0 } -func (*PolicyMergeOperation_AddRule) isPolicyMergeOperation_Operation() {} - -func (*PolicyMergeOperation_RemoveEndpoint) isPolicyMergeOperation_Operation() {} - -func (*PolicyMergeOperation_RemoveRule) isPolicyMergeOperation_Operation() {} - -func (*PolicyMergeOperation_AddDenyRules) isPolicyMergeOperation_Operation() {} - -func (*PolicyMergeOperation_AddAllowRules) isPolicyMergeOperation_Operation() {} - -func (*PolicyMergeOperation_RemoveBinary) isPolicyMergeOperation_Operation() {} - -type AddNetworkRule struct { - state protoimpl.MessageState `protogen:"open.v1"` - RuleName string `protobuf:"bytes,1,opt,name=rule_name,json=ruleName,proto3" json:"rule_name,omitempty"` - Rule *sandboxv1.NetworkPolicyRule `protobuf:"bytes,2,opt,name=rule,proto3" json:"rule,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +type GetConfigUpdateOperationRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + OperationId string `protobuf:"bytes,1,opt,name=operation_id,json=operationId,proto3" json:"operation_id,omitempty"` + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *AddNetworkRule) Reset() { - *x = AddNetworkRule{} +func (x *GetConfigUpdateOperationRequest) Reset() { + *x = GetConfigUpdateOperationRequest{} mi := &file_openshell_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AddNetworkRule) String() string { +func (x *GetConfigUpdateOperationRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AddNetworkRule) ProtoMessage() {} +func (*GetConfigUpdateOperationRequest) ProtoMessage() {} -func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { +func (x *GetConfigUpdateOperationRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -9571,48 +9683,46 @@ func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AddNetworkRule.ProtoReflect.Descriptor instead. -func (*AddNetworkRule) Descriptor() ([]byte, []int) { +// Deprecated: Use GetConfigUpdateOperationRequest.ProtoReflect.Descriptor instead. +func (*GetConfigUpdateOperationRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{131} } -func (x *AddNetworkRule) GetRuleName() string { +func (x *GetConfigUpdateOperationRequest) GetOperationId() string { if x != nil { - return x.RuleName + return x.OperationId } return "" } -func (x *AddNetworkRule) GetRule() *sandboxv1.NetworkPolicyRule { +func (x *GetConfigUpdateOperationRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.Rule + return x.WorkspaceScope } return nil } -type RemoveNetworkEndpoint struct { +type GetConfigUpdateOperationResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - RuleName string `protobuf:"bytes,1,opt,name=rule_name,json=ruleName,proto3" json:"rule_name,omitempty"` - Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"` - Port uint32 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"` + Operation *ConfigUpdateOperation `protobuf:"bytes,1,opt,name=operation,proto3" json:"operation,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *RemoveNetworkEndpoint) Reset() { - *x = RemoveNetworkEndpoint{} +func (x *GetConfigUpdateOperationResponse) Reset() { + *x = GetConfigUpdateOperationResponse{} mi := &file_openshell_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *RemoveNetworkEndpoint) String() string { +func (x *GetConfigUpdateOperationResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RemoveNetworkEndpoint) ProtoMessage() {} +func (*GetConfigUpdateOperationResponse) ProtoMessage() {} -func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { +func (x *GetConfigUpdateOperationResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -9624,27 +9734,271 @@ func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RemoveNetworkEndpoint.ProtoReflect.Descriptor instead. -func (*RemoveNetworkEndpoint) Descriptor() ([]byte, []int) { +// Deprecated: Use GetConfigUpdateOperationResponse.ProtoReflect.Descriptor instead. +func (*GetConfigUpdateOperationResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{132} } -func (x *RemoveNetworkEndpoint) GetRuleName() string { - if x != nil { - return x.RuleName - } - return "" -} - -func (x *RemoveNetworkEndpoint) GetHost() string { +func (x *GetConfigUpdateOperationResponse) GetOperation() *ConfigUpdateOperation { if x != nil { - return x.Host + return x.Operation } - return "" + return nil } -func (x *RemoveNetworkEndpoint) GetPort() uint32 { - if x != nil { +type PolicyMergeOperation struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Operation: + // + // *PolicyMergeOperation_AddRule + // *PolicyMergeOperation_RemoveEndpoint + // *PolicyMergeOperation_RemoveRule + // *PolicyMergeOperation_AddDenyRules + // *PolicyMergeOperation_AddAllowRules + // *PolicyMergeOperation_RemoveBinary + Operation isPolicyMergeOperation_Operation `protobuf_oneof:"operation"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyMergeOperation) Reset() { + *x = PolicyMergeOperation{} + mi := &file_openshell_proto_msgTypes[133] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyMergeOperation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyMergeOperation) ProtoMessage() {} + +func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[133] + 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 PolicyMergeOperation.ProtoReflect.Descriptor instead. +func (*PolicyMergeOperation) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{133} +} + +func (x *PolicyMergeOperation) GetOperation() isPolicyMergeOperation_Operation { + if x != nil { + return x.Operation + } + return nil +} + +func (x *PolicyMergeOperation) GetAddRule() *AddNetworkRule { + if x != nil { + if x, ok := x.Operation.(*PolicyMergeOperation_AddRule); ok { + return x.AddRule + } + } + return nil +} + +func (x *PolicyMergeOperation) GetRemoveEndpoint() *RemoveNetworkEndpoint { + if x != nil { + if x, ok := x.Operation.(*PolicyMergeOperation_RemoveEndpoint); ok { + return x.RemoveEndpoint + } + } + return nil +} + +func (x *PolicyMergeOperation) GetRemoveRule() *RemoveNetworkRule { + if x != nil { + if x, ok := x.Operation.(*PolicyMergeOperation_RemoveRule); ok { + return x.RemoveRule + } + } + return nil +} + +func (x *PolicyMergeOperation) GetAddDenyRules() *AddDenyRules { + if x != nil { + if x, ok := x.Operation.(*PolicyMergeOperation_AddDenyRules); ok { + return x.AddDenyRules + } + } + return nil +} + +func (x *PolicyMergeOperation) GetAddAllowRules() *AddAllowRules { + if x != nil { + if x, ok := x.Operation.(*PolicyMergeOperation_AddAllowRules); ok { + return x.AddAllowRules + } + } + return nil +} + +func (x *PolicyMergeOperation) GetRemoveBinary() *RemoveNetworkBinary { + if x != nil { + if x, ok := x.Operation.(*PolicyMergeOperation_RemoveBinary); ok { + return x.RemoveBinary + } + } + return nil +} + +type isPolicyMergeOperation_Operation interface { + isPolicyMergeOperation_Operation() +} + +type PolicyMergeOperation_AddRule struct { + AddRule *AddNetworkRule `protobuf:"bytes,1,opt,name=add_rule,json=addRule,proto3,oneof"` +} + +type PolicyMergeOperation_RemoveEndpoint struct { + RemoveEndpoint *RemoveNetworkEndpoint `protobuf:"bytes,2,opt,name=remove_endpoint,json=removeEndpoint,proto3,oneof"` +} + +type PolicyMergeOperation_RemoveRule struct { + RemoveRule *RemoveNetworkRule `protobuf:"bytes,3,opt,name=remove_rule,json=removeRule,proto3,oneof"` +} + +type PolicyMergeOperation_AddDenyRules struct { + AddDenyRules *AddDenyRules `protobuf:"bytes,4,opt,name=add_deny_rules,json=addDenyRules,proto3,oneof"` +} + +type PolicyMergeOperation_AddAllowRules struct { + AddAllowRules *AddAllowRules `protobuf:"bytes,5,opt,name=add_allow_rules,json=addAllowRules,proto3,oneof"` +} + +type PolicyMergeOperation_RemoveBinary struct { + RemoveBinary *RemoveNetworkBinary `protobuf:"bytes,6,opt,name=remove_binary,json=removeBinary,proto3,oneof"` +} + +func (*PolicyMergeOperation_AddRule) isPolicyMergeOperation_Operation() {} + +func (*PolicyMergeOperation_RemoveEndpoint) isPolicyMergeOperation_Operation() {} + +func (*PolicyMergeOperation_RemoveRule) isPolicyMergeOperation_Operation() {} + +func (*PolicyMergeOperation_AddDenyRules) isPolicyMergeOperation_Operation() {} + +func (*PolicyMergeOperation_AddAllowRules) isPolicyMergeOperation_Operation() {} + +func (*PolicyMergeOperation_RemoveBinary) isPolicyMergeOperation_Operation() {} + +type AddNetworkRule struct { + state protoimpl.MessageState `protogen:"open.v1"` + RuleName string `protobuf:"bytes,1,opt,name=rule_name,json=ruleName,proto3" json:"rule_name,omitempty"` + Rule *sandboxv1.NetworkPolicyRule `protobuf:"bytes,2,opt,name=rule,proto3" json:"rule,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AddNetworkRule) Reset() { + *x = AddNetworkRule{} + mi := &file_openshell_proto_msgTypes[134] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddNetworkRule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddNetworkRule) ProtoMessage() {} + +func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[134] + 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 AddNetworkRule.ProtoReflect.Descriptor instead. +func (*AddNetworkRule) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{134} +} + +func (x *AddNetworkRule) GetRuleName() string { + if x != nil { + return x.RuleName + } + return "" +} + +func (x *AddNetworkRule) GetRule() *sandboxv1.NetworkPolicyRule { + if x != nil { + return x.Rule + } + return nil +} + +type RemoveNetworkEndpoint struct { + state protoimpl.MessageState `protogen:"open.v1"` + RuleName string `protobuf:"bytes,1,opt,name=rule_name,json=ruleName,proto3" json:"rule_name,omitempty"` + Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"` + Port uint32 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveNetworkEndpoint) Reset() { + *x = RemoveNetworkEndpoint{} + mi := &file_openshell_proto_msgTypes[135] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveNetworkEndpoint) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveNetworkEndpoint) ProtoMessage() {} + +func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[135] + 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 RemoveNetworkEndpoint.ProtoReflect.Descriptor instead. +func (*RemoveNetworkEndpoint) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{135} +} + +func (x *RemoveNetworkEndpoint) GetRuleName() string { + if x != nil { + return x.RuleName + } + return "" +} + +func (x *RemoveNetworkEndpoint) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *RemoveNetworkEndpoint) GetPort() uint32 { + if x != nil { return x.Port } return 0 @@ -9659,7 +10013,7 @@ type RemoveNetworkRule struct { func (x *RemoveNetworkRule) Reset() { *x = RemoveNetworkRule{} - mi := &file_openshell_proto_msgTypes[133] + mi := &file_openshell_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9671,7 +10025,7 @@ func (x *RemoveNetworkRule) String() string { func (*RemoveNetworkRule) ProtoMessage() {} func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[133] + mi := &file_openshell_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9684,7 +10038,7 @@ func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkRule.ProtoReflect.Descriptor instead. func (*RemoveNetworkRule) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{133} + return file_openshell_proto_rawDescGZIP(), []int{136} } func (x *RemoveNetworkRule) GetRuleName() string { @@ -9705,7 +10059,7 @@ type AddDenyRules struct { func (x *AddDenyRules) Reset() { *x = AddDenyRules{} - mi := &file_openshell_proto_msgTypes[134] + mi := &file_openshell_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9717,7 +10071,7 @@ func (x *AddDenyRules) String() string { func (*AddDenyRules) ProtoMessage() {} func (x *AddDenyRules) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[134] + mi := &file_openshell_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9730,7 +10084,7 @@ func (x *AddDenyRules) ProtoReflect() protoreflect.Message { // Deprecated: Use AddDenyRules.ProtoReflect.Descriptor instead. func (*AddDenyRules) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{134} + return file_openshell_proto_rawDescGZIP(), []int{137} } func (x *AddDenyRules) GetHost() string { @@ -9765,7 +10119,7 @@ type AddAllowRules struct { func (x *AddAllowRules) Reset() { *x = AddAllowRules{} - mi := &file_openshell_proto_msgTypes[135] + mi := &file_openshell_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9777,7 +10131,7 @@ func (x *AddAllowRules) String() string { func (*AddAllowRules) ProtoMessage() {} func (x *AddAllowRules) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[135] + mi := &file_openshell_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9790,7 +10144,7 @@ func (x *AddAllowRules) ProtoReflect() protoreflect.Message { // Deprecated: Use AddAllowRules.ProtoReflect.Descriptor instead. func (*AddAllowRules) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{135} + return file_openshell_proto_rawDescGZIP(), []int{138} } func (x *AddAllowRules) GetHost() string { @@ -9824,7 +10178,7 @@ type RemoveNetworkBinary struct { func (x *RemoveNetworkBinary) Reset() { *x = RemoveNetworkBinary{} - mi := &file_openshell_proto_msgTypes[136] + mi := &file_openshell_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9836,7 +10190,7 @@ func (x *RemoveNetworkBinary) String() string { func (*RemoveNetworkBinary) ProtoMessage() {} func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[136] + mi := &file_openshell_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9849,7 +10203,7 @@ func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkBinary.ProtoReflect.Descriptor instead. func (*RemoveNetworkBinary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{136} + return file_openshell_proto_rawDescGZIP(), []int{139} } func (x *RemoveNetworkBinary) GetRuleName() string { @@ -9878,14 +10232,17 @@ type UpdateConfigResponse struct { // True when a setting delete operation removed an existing key. Deleted bool `protobuf:"varint,4,opt,name=deleted,proto3" json:"deleted,omitempty"` // Sandbox metadata annotations after the update. Empty for global updates. - Annotations map[string]string `protobuf:"bytes,5,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Annotations map[string]string `protobuf:"bytes,5,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Durable apply operation for a changed sandbox-scoped mutation. Omitted + // for global changes and idempotent no-op updates. + Operation *ConfigUpdateOperation `protobuf:"bytes,6,opt,name=operation,proto3" json:"operation,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdateConfigResponse) Reset() { *x = UpdateConfigResponse{} - mi := &file_openshell_proto_msgTypes[137] + mi := &file_openshell_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9897,7 +10254,7 @@ func (x *UpdateConfigResponse) String() string { func (*UpdateConfigResponse) ProtoMessage() {} func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[137] + mi := &file_openshell_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9910,7 +10267,7 @@ func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateConfigResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{137} + return file_openshell_proto_rawDescGZIP(), []int{140} } func (x *UpdateConfigResponse) GetVersion() uint32 { @@ -9948,6 +10305,13 @@ func (x *UpdateConfigResponse) GetAnnotations() map[string]string { return nil } +func (x *UpdateConfigResponse) GetOperation() *ConfigUpdateOperation { + if x != nil { + return x.Operation + } + return nil +} + // Get sandbox policy status request. type GetSandboxPolicyStatusRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -9966,7 +10330,7 @@ type GetSandboxPolicyStatusRequest struct { func (x *GetSandboxPolicyStatusRequest) Reset() { *x = GetSandboxPolicyStatusRequest{} - mi := &file_openshell_proto_msgTypes[138] + mi := &file_openshell_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9978,7 +10342,7 @@ func (x *GetSandboxPolicyStatusRequest) String() string { func (*GetSandboxPolicyStatusRequest) ProtoMessage() {} func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[138] + mi := &file_openshell_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9991,7 +10355,7 @@ func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxPolicyStatusRequest.ProtoReflect.Descriptor instead. func (*GetSandboxPolicyStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{138} + return file_openshell_proto_rawDescGZIP(), []int{141} } func (x *GetSandboxPolicyStatusRequest) GetName() string { @@ -10035,7 +10399,7 @@ type GetSandboxPolicyStatusResponse struct { func (x *GetSandboxPolicyStatusResponse) Reset() { *x = GetSandboxPolicyStatusResponse{} - mi := &file_openshell_proto_msgTypes[139] + mi := &file_openshell_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10047,7 +10411,7 @@ func (x *GetSandboxPolicyStatusResponse) String() string { func (*GetSandboxPolicyStatusResponse) ProtoMessage() {} func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[139] + mi := &file_openshell_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10060,7 +10424,7 @@ func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxPolicyStatusResponse.ProtoReflect.Descriptor instead. func (*GetSandboxPolicyStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{139} + return file_openshell_proto_rawDescGZIP(), []int{142} } func (x *GetSandboxPolicyStatusResponse) GetRevision() *SandboxPolicyRevision { @@ -10099,7 +10463,7 @@ type ListSandboxPoliciesRequest struct { func (x *ListSandboxPoliciesRequest) Reset() { *x = ListSandboxPoliciesRequest{} - mi := &file_openshell_proto_msgTypes[140] + mi := &file_openshell_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10111,7 +10475,7 @@ func (x *ListSandboxPoliciesRequest) String() string { func (*ListSandboxPoliciesRequest) ProtoMessage() {} func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[140] + mi := &file_openshell_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10124,7 +10488,7 @@ func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxPoliciesRequest.ProtoReflect.Descriptor instead. func (*ListSandboxPoliciesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{140} + return file_openshell_proto_rawDescGZIP(), []int{143} } func (x *ListSandboxPoliciesRequest) GetName() string { @@ -10176,7 +10540,7 @@ type ListSandboxPoliciesResponse struct { func (x *ListSandboxPoliciesResponse) Reset() { *x = ListSandboxPoliciesResponse{} - mi := &file_openshell_proto_msgTypes[141] + mi := &file_openshell_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10188,7 +10552,7 @@ func (x *ListSandboxPoliciesResponse) String() string { func (*ListSandboxPoliciesResponse) ProtoMessage() {} func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[141] + mi := &file_openshell_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10201,7 +10565,7 @@ func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxPoliciesResponse.ProtoReflect.Descriptor instead. func (*ListSandboxPoliciesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{141} + return file_openshell_proto_rawDescGZIP(), []int{144} } func (x *ListSandboxPoliciesResponse) GetRevisions() []*SandboxPolicyRevision { @@ -10235,7 +10599,7 @@ type ReportPolicyStatusRequest struct { func (x *ReportPolicyStatusRequest) Reset() { *x = ReportPolicyStatusRequest{} - mi := &file_openshell_proto_msgTypes[142] + mi := &file_openshell_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10247,7 +10611,7 @@ func (x *ReportPolicyStatusRequest) String() string { func (*ReportPolicyStatusRequest) ProtoMessage() {} func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[142] + mi := &file_openshell_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10260,7 +10624,7 @@ func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportPolicyStatusRequest.ProtoReflect.Descriptor instead. func (*ReportPolicyStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{142} + return file_openshell_proto_rawDescGZIP(), []int{145} } func (x *ReportPolicyStatusRequest) GetSandboxId() string { @@ -10300,7 +10664,7 @@ type ReportPolicyStatusResponse struct { func (x *ReportPolicyStatusResponse) Reset() { *x = ReportPolicyStatusResponse{} - mi := &file_openshell_proto_msgTypes[143] + mi := &file_openshell_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10312,7 +10676,7 @@ func (x *ReportPolicyStatusResponse) String() string { func (*ReportPolicyStatusResponse) ProtoMessage() {} func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[143] + mi := &file_openshell_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10325,7 +10689,7 @@ func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportPolicyStatusResponse.ProtoReflect.Descriptor instead. func (*ReportPolicyStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{143} + return file_openshell_proto_rawDescGZIP(), []int{146} } // A versioned policy revision with metadata. @@ -10358,7 +10722,7 @@ type SandboxPolicyRevision struct { func (x *SandboxPolicyRevision) Reset() { *x = SandboxPolicyRevision{} - mi := &file_openshell_proto_msgTypes[144] + mi := &file_openshell_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10370,7 +10734,7 @@ func (x *SandboxPolicyRevision) String() string { func (*SandboxPolicyRevision) ProtoMessage() {} func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[144] + mi := &file_openshell_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10383,7 +10747,7 @@ func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxPolicyRevision.ProtoReflect.Descriptor instead. func (*SandboxPolicyRevision) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{144} + return file_openshell_proto_rawDescGZIP(), []int{147} } func (x *SandboxPolicyRevision) GetVersion() uint32 { @@ -10463,7 +10827,7 @@ type GetSandboxLogsRequest struct { func (x *GetSandboxLogsRequest) Reset() { *x = GetSandboxLogsRequest{} - mi := &file_openshell_proto_msgTypes[145] + mi := &file_openshell_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10475,7 +10839,7 @@ func (x *GetSandboxLogsRequest) String() string { func (*GetSandboxLogsRequest) ProtoMessage() {} func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[145] + mi := &file_openshell_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10488,7 +10852,7 @@ func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxLogsRequest.ProtoReflect.Descriptor instead. func (*GetSandboxLogsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{145} + return file_openshell_proto_rawDescGZIP(), []int{148} } func (x *GetSandboxLogsRequest) GetSandboxId() string { @@ -10546,7 +10910,7 @@ type PushSandboxLogsRequest struct { func (x *PushSandboxLogsRequest) Reset() { *x = PushSandboxLogsRequest{} - mi := &file_openshell_proto_msgTypes[146] + mi := &file_openshell_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10558,7 +10922,7 @@ func (x *PushSandboxLogsRequest) String() string { func (*PushSandboxLogsRequest) ProtoMessage() {} func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[146] + mi := &file_openshell_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10571,7 +10935,7 @@ func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PushSandboxLogsRequest.ProtoReflect.Descriptor instead. func (*PushSandboxLogsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{146} + return file_openshell_proto_rawDescGZIP(), []int{149} } func (x *PushSandboxLogsRequest) GetSandboxId() string { @@ -10597,7 +10961,7 @@ type PushSandboxLogsResponse struct { func (x *PushSandboxLogsResponse) Reset() { *x = PushSandboxLogsResponse{} - mi := &file_openshell_proto_msgTypes[147] + mi := &file_openshell_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10609,7 +10973,7 @@ func (x *PushSandboxLogsResponse) String() string { func (*PushSandboxLogsResponse) ProtoMessage() {} func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[147] + mi := &file_openshell_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10622,7 +10986,7 @@ func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PushSandboxLogsResponse.ProtoReflect.Descriptor instead. func (*PushSandboxLogsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{147} + return file_openshell_proto_rawDescGZIP(), []int{150} } // Get sandbox logs response. @@ -10638,7 +11002,7 @@ type GetSandboxLogsResponse struct { func (x *GetSandboxLogsResponse) Reset() { *x = GetSandboxLogsResponse{} - mi := &file_openshell_proto_msgTypes[148] + mi := &file_openshell_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10650,7 +11014,7 @@ func (x *GetSandboxLogsResponse) String() string { func (*GetSandboxLogsResponse) ProtoMessage() {} func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[148] + mi := &file_openshell_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10663,7 +11027,7 @@ func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxLogsResponse.ProtoReflect.Descriptor instead. func (*GetSandboxLogsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{148} + return file_openshell_proto_rawDescGZIP(), []int{151} } func (x *GetSandboxLogsResponse) GetLogs() []*SandboxLogLine { @@ -10698,7 +11062,7 @@ type SupervisorMessage struct { func (x *SupervisorMessage) Reset() { *x = SupervisorMessage{} - mi := &file_openshell_proto_msgTypes[149] + mi := &file_openshell_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10710,7 +11074,7 @@ func (x *SupervisorMessage) String() string { func (*SupervisorMessage) ProtoMessage() {} func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[149] + mi := &file_openshell_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10723,7 +11087,7 @@ func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorMessage.ProtoReflect.Descriptor instead. func (*SupervisorMessage) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{149} + return file_openshell_proto_rawDescGZIP(), []int{152} } func (x *SupervisorMessage) GetPayload() isSupervisorMessage_Payload { @@ -10845,7 +11209,7 @@ type GatewayMessage struct { func (x *GatewayMessage) Reset() { *x = GatewayMessage{} - mi := &file_openshell_proto_msgTypes[150] + mi := &file_openshell_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10857,7 +11221,7 @@ func (x *GatewayMessage) String() string { func (*GatewayMessage) ProtoMessage() {} func (x *GatewayMessage) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[150] + mi := &file_openshell_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10870,7 +11234,7 @@ func (x *GatewayMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayMessage.ProtoReflect.Descriptor instead. func (*GatewayMessage) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{150} + return file_openshell_proto_rawDescGZIP(), []int{153} } func (x *GatewayMessage) GetPayload() isGatewayMessage_Payload { @@ -10990,7 +11354,7 @@ type SupervisorHello struct { func (x *SupervisorHello) Reset() { *x = SupervisorHello{} - mi := &file_openshell_proto_msgTypes[151] + mi := &file_openshell_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11002,7 +11366,7 @@ func (x *SupervisorHello) String() string { func (*SupervisorHello) ProtoMessage() {} func (x *SupervisorHello) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[151] + mi := &file_openshell_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11015,7 +11379,7 @@ func (x *SupervisorHello) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorHello.ProtoReflect.Descriptor instead. func (*SupervisorHello) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{151} + return file_openshell_proto_rawDescGZIP(), []int{154} } func (x *SupervisorHello) GetSandboxId() string { @@ -11058,7 +11422,7 @@ type SessionAccepted struct { func (x *SessionAccepted) Reset() { *x = SessionAccepted{} - mi := &file_openshell_proto_msgTypes[152] + mi := &file_openshell_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11070,7 +11434,7 @@ func (x *SessionAccepted) String() string { func (*SessionAccepted) ProtoMessage() {} func (x *SessionAccepted) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[152] + mi := &file_openshell_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11083,7 +11447,7 @@ func (x *SessionAccepted) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionAccepted.ProtoReflect.Descriptor instead. func (*SessionAccepted) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{152} + return file_openshell_proto_rawDescGZIP(), []int{155} } func (x *SessionAccepted) GetSessionId() string { @@ -11125,7 +11489,7 @@ type ConfigBootstrap struct { func (x *ConfigBootstrap) Reset() { *x = ConfigBootstrap{} - mi := &file_openshell_proto_msgTypes[153] + mi := &file_openshell_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11137,7 +11501,7 @@ func (x *ConfigBootstrap) String() string { func (*ConfigBootstrap) ProtoMessage() {} func (x *ConfigBootstrap) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[153] + mi := &file_openshell_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11150,7 +11514,7 @@ func (x *ConfigBootstrap) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigBootstrap.ProtoReflect.Descriptor instead. func (*ConfigBootstrap) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{153} + return file_openshell_proto_rawDescGZIP(), []int{156} } func (x *ConfigBootstrap) GetSandboxConfig() *sandboxv1.SandboxConfigSnapshot { @@ -11186,7 +11550,7 @@ type ConfigUpdate struct { func (x *ConfigUpdate) Reset() { *x = ConfigUpdate{} - mi := &file_openshell_proto_msgTypes[154] + mi := &file_openshell_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11198,7 +11562,7 @@ func (x *ConfigUpdate) String() string { func (*ConfigUpdate) ProtoMessage() {} func (x *ConfigUpdate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[154] + mi := &file_openshell_proto_msgTypes[157] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11211,7 +11575,7 @@ func (x *ConfigUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigUpdate.ProtoReflect.Descriptor instead. func (*ConfigUpdate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{154} + return file_openshell_proto_rawDescGZIP(), []int{157} } func (x *ConfigUpdate) GetUpdateId() string { @@ -11284,7 +11648,7 @@ type ConfigSnapshotRevision struct { func (x *ConfigSnapshotRevision) Reset() { *x = ConfigSnapshotRevision{} - mi := &file_openshell_proto_msgTypes[155] + mi := &file_openshell_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11296,7 +11660,7 @@ func (x *ConfigSnapshotRevision) String() string { func (*ConfigSnapshotRevision) ProtoMessage() {} func (x *ConfigSnapshotRevision) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[155] + mi := &file_openshell_proto_msgTypes[158] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11309,7 +11673,7 @@ func (x *ConfigSnapshotRevision) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigSnapshotRevision.ProtoReflect.Descriptor instead. func (*ConfigSnapshotRevision) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{155} + return file_openshell_proto_rawDescGZIP(), []int{158} } func (x *ConfigSnapshotRevision) GetComponent() isConfigSnapshotRevision_Component { @@ -11361,13 +11725,16 @@ type SandboxConfigRevision struct { PolicyVersion uint32 `protobuf:"varint,2,opt,name=policy_version,json=policyVersion,proto3" json:"policy_version,omitempty"` PolicySource sandboxv1.PolicySource `protobuf:"varint,3,opt,name=policy_source,json=policySource,proto3,enum=openshell.sandbox.v1.PolicySource" json:"policy_source,omitempty"` GlobalPolicyVersion uint32 `protobuf:"varint,4,opt,name=global_policy_version,json=globalPolicyVersion,proto3" json:"global_policy_version,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Monotonic revision of the sandbox-scoped settings row. This disambiguates + // setting operations whose effective config fingerprint is equality-only. + SettingsRevision uint64 `protobuf:"varint,5,opt,name=settings_revision,json=settingsRevision,proto3" json:"settings_revision,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SandboxConfigRevision) Reset() { *x = SandboxConfigRevision{} - mi := &file_openshell_proto_msgTypes[156] + mi := &file_openshell_proto_msgTypes[159] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11379,7 +11746,7 @@ func (x *SandboxConfigRevision) String() string { func (*SandboxConfigRevision) ProtoMessage() {} func (x *SandboxConfigRevision) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[156] + mi := &file_openshell_proto_msgTypes[159] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11392,7 +11759,7 @@ func (x *SandboxConfigRevision) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxConfigRevision.ProtoReflect.Descriptor instead. func (*SandboxConfigRevision) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{156} + return file_openshell_proto_rawDescGZIP(), []int{159} } func (x *SandboxConfigRevision) GetConfigRevision() uint64 { @@ -11423,6 +11790,13 @@ func (x *SandboxConfigRevision) GetGlobalPolicyVersion() uint32 { return 0 } +func (x *SandboxConfigRevision) GetSettingsRevision() uint64 { + if x != nil { + return x.SettingsRevision + } + return 0 +} + // Sanitized application failure. Messages must not contain configuration // payloads, credentials, or provider values. type ConfigApplyFailure struct { @@ -11436,7 +11810,7 @@ type ConfigApplyFailure struct { func (x *ConfigApplyFailure) Reset() { *x = ConfigApplyFailure{} - mi := &file_openshell_proto_msgTypes[157] + mi := &file_openshell_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11448,7 +11822,7 @@ func (x *ConfigApplyFailure) String() string { func (*ConfigApplyFailure) ProtoMessage() {} func (x *ConfigApplyFailure) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[157] + mi := &file_openshell_proto_msgTypes[160] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11461,7 +11835,7 @@ func (x *ConfigApplyFailure) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigApplyFailure.ProtoReflect.Descriptor instead. func (*ConfigApplyFailure) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{157} + return file_openshell_proto_rawDescGZIP(), []int{160} } func (x *ConfigApplyFailure) GetCode() string { @@ -11501,7 +11875,7 @@ type ConfigComponentApplyResult struct { func (x *ConfigComponentApplyResult) Reset() { *x = ConfigComponentApplyResult{} - mi := &file_openshell_proto_msgTypes[158] + mi := &file_openshell_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11513,7 +11887,7 @@ func (x *ConfigComponentApplyResult) String() string { func (*ConfigComponentApplyResult) ProtoMessage() {} func (x *ConfigComponentApplyResult) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[158] + mi := &file_openshell_proto_msgTypes[161] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11526,7 +11900,7 @@ func (x *ConfigComponentApplyResult) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigComponentApplyResult.ProtoReflect.Descriptor instead. func (*ConfigComponentApplyResult) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{158} + return file_openshell_proto_rawDescGZIP(), []int{161} } func (x *ConfigComponentApplyResult) GetComponent() ConfigComponent { @@ -11578,7 +11952,7 @@ type ConfigUpdateResult struct { func (x *ConfigUpdateResult) Reset() { *x = ConfigUpdateResult{} - mi := &file_openshell_proto_msgTypes[159] + mi := &file_openshell_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11590,7 +11964,7 @@ func (x *ConfigUpdateResult) String() string { func (*ConfigUpdateResult) ProtoMessage() {} func (x *ConfigUpdateResult) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[159] + mi := &file_openshell_proto_msgTypes[162] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11603,7 +11977,7 @@ func (x *ConfigUpdateResult) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigUpdateResult.ProtoReflect.Descriptor instead. func (*ConfigUpdateResult) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{159} + return file_openshell_proto_rawDescGZIP(), []int{162} } func (x *ConfigUpdateResult) GetUpdateId() string { @@ -11637,7 +12011,7 @@ type ConfigBootstrapResult struct { func (x *ConfigBootstrapResult) Reset() { *x = ConfigBootstrapResult{} - mi := &file_openshell_proto_msgTypes[160] + mi := &file_openshell_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11649,7 +12023,7 @@ func (x *ConfigBootstrapResult) String() string { func (*ConfigBootstrapResult) ProtoMessage() {} func (x *ConfigBootstrapResult) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[160] + mi := &file_openshell_proto_msgTypes[163] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11662,7 +12036,7 @@ func (x *ConfigBootstrapResult) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigBootstrapResult.ProtoReflect.Descriptor instead. func (*ConfigBootstrapResult) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{160} + return file_openshell_proto_rawDescGZIP(), []int{163} } func (x *ConfigBootstrapResult) GetResults() []*ConfigComponentApplyResult { @@ -11683,7 +12057,7 @@ type SessionRejected struct { func (x *SessionRejected) Reset() { *x = SessionRejected{} - mi := &file_openshell_proto_msgTypes[161] + mi := &file_openshell_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11695,7 +12069,7 @@ func (x *SessionRejected) String() string { func (*SessionRejected) ProtoMessage() {} func (x *SessionRejected) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[161] + mi := &file_openshell_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11708,7 +12082,7 @@ func (x *SessionRejected) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionRejected.ProtoReflect.Descriptor instead. func (*SessionRejected) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{161} + return file_openshell_proto_rawDescGZIP(), []int{164} } func (x *SessionRejected) GetReason() string { @@ -11727,7 +12101,7 @@ type SupervisorHeartbeat struct { func (x *SupervisorHeartbeat) Reset() { *x = SupervisorHeartbeat{} - mi := &file_openshell_proto_msgTypes[162] + mi := &file_openshell_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11739,7 +12113,7 @@ func (x *SupervisorHeartbeat) String() string { func (*SupervisorHeartbeat) ProtoMessage() {} func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[162] + mi := &file_openshell_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11752,7 +12126,7 @@ func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorHeartbeat.ProtoReflect.Descriptor instead. func (*SupervisorHeartbeat) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{162} + return file_openshell_proto_rawDescGZIP(), []int{165} } // Gateway heartbeat. @@ -11764,7 +12138,7 @@ type GatewayHeartbeat struct { func (x *GatewayHeartbeat) Reset() { *x = GatewayHeartbeat{} - mi := &file_openshell_proto_msgTypes[163] + mi := &file_openshell_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11776,7 +12150,7 @@ func (x *GatewayHeartbeat) String() string { func (*GatewayHeartbeat) ProtoMessage() {} func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[163] + mi := &file_openshell_proto_msgTypes[166] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11789,7 +12163,7 @@ func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayHeartbeat.ProtoReflect.Descriptor instead. func (*GatewayHeartbeat) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{163} + return file_openshell_proto_rawDescGZIP(), []int{166} } // Terminal result reported before the supervisor shuts down. A successful RPC @@ -11806,7 +12180,7 @@ type ReportMainProcessExitRequest struct { func (x *ReportMainProcessExitRequest) Reset() { *x = ReportMainProcessExitRequest{} - mi := &file_openshell_proto_msgTypes[164] + mi := &file_openshell_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11818,7 +12192,7 @@ func (x *ReportMainProcessExitRequest) String() string { func (*ReportMainProcessExitRequest) ProtoMessage() {} func (x *ReportMainProcessExitRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[164] + mi := &file_openshell_proto_msgTypes[167] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11831,7 +12205,7 @@ func (x *ReportMainProcessExitRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportMainProcessExitRequest.ProtoReflect.Descriptor instead. func (*ReportMainProcessExitRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{164} + return file_openshell_proto_rawDescGZIP(), []int{167} } func (x *ReportMainProcessExitRequest) GetSandboxId() string { @@ -11863,7 +12237,7 @@ type ReportMainProcessExitResponse struct { func (x *ReportMainProcessExitResponse) Reset() { *x = ReportMainProcessExitResponse{} - mi := &file_openshell_proto_msgTypes[165] + mi := &file_openshell_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11875,7 +12249,7 @@ func (x *ReportMainProcessExitResponse) String() string { func (*ReportMainProcessExitResponse) ProtoMessage() {} func (x *ReportMainProcessExitResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[165] + mi := &file_openshell_proto_msgTypes[168] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11888,7 +12262,7 @@ func (x *ReportMainProcessExitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportMainProcessExitResponse.ProtoReflect.Descriptor instead. func (*ReportMainProcessExitResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{165} + return file_openshell_proto_rawDescGZIP(), []int{168} } // Terminal-delivery completion reported after all expected foreground SSH @@ -11903,7 +12277,7 @@ type FinalizeMainProcessExitRequest struct { func (x *FinalizeMainProcessExitRequest) Reset() { *x = FinalizeMainProcessExitRequest{} - mi := &file_openshell_proto_msgTypes[166] + mi := &file_openshell_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11915,7 +12289,7 @@ func (x *FinalizeMainProcessExitRequest) String() string { func (*FinalizeMainProcessExitRequest) ProtoMessage() {} func (x *FinalizeMainProcessExitRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[166] + mi := &file_openshell_proto_msgTypes[169] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11928,7 +12302,7 @@ func (x *FinalizeMainProcessExitRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizeMainProcessExitRequest.ProtoReflect.Descriptor instead. func (*FinalizeMainProcessExitRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{166} + return file_openshell_proto_rawDescGZIP(), []int{169} } func (x *FinalizeMainProcessExitRequest) GetSandboxId() string { @@ -11953,7 +12327,7 @@ type FinalizeMainProcessExitResponse struct { func (x *FinalizeMainProcessExitResponse) Reset() { *x = FinalizeMainProcessExitResponse{} - mi := &file_openshell_proto_msgTypes[167] + mi := &file_openshell_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11965,7 +12339,7 @@ func (x *FinalizeMainProcessExitResponse) String() string { func (*FinalizeMainProcessExitResponse) ProtoMessage() {} func (x *FinalizeMainProcessExitResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[167] + mi := &file_openshell_proto_msgTypes[170] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11978,7 +12352,7 @@ func (x *FinalizeMainProcessExitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizeMainProcessExitResponse.ProtoReflect.Descriptor instead. func (*FinalizeMainProcessExitResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{167} + return file_openshell_proto_rawDescGZIP(), []int{170} } // Gateway requests the supervisor to open a relay channel. @@ -12007,7 +12381,7 @@ type RelayOpen struct { func (x *RelayOpen) Reset() { *x = RelayOpen{} - mi := &file_openshell_proto_msgTypes[168] + mi := &file_openshell_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12019,7 +12393,7 @@ func (x *RelayOpen) String() string { func (*RelayOpen) ProtoMessage() {} func (x *RelayOpen) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[168] + mi := &file_openshell_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12032,7 +12406,7 @@ func (x *RelayOpen) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpen.ProtoReflect.Descriptor instead. func (*RelayOpen) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{168} + return file_openshell_proto_rawDescGZIP(), []int{171} } func (x *RelayOpen) GetChannelId() string { @@ -12099,7 +12473,7 @@ type SshRelayTarget struct { func (x *SshRelayTarget) Reset() { *x = SshRelayTarget{} - mi := &file_openshell_proto_msgTypes[169] + mi := &file_openshell_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12111,7 +12485,7 @@ func (x *SshRelayTarget) String() string { func (*SshRelayTarget) ProtoMessage() {} func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[169] + mi := &file_openshell_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12124,7 +12498,7 @@ func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use SshRelayTarget.ProtoReflect.Descriptor instead. func (*SshRelayTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{169} + return file_openshell_proto_rawDescGZIP(), []int{172} } // TCP target dialed by the supervisor from inside the sandbox. @@ -12140,7 +12514,7 @@ type TcpRelayTarget struct { func (x *TcpRelayTarget) Reset() { *x = TcpRelayTarget{} - mi := &file_openshell_proto_msgTypes[170] + mi := &file_openshell_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12152,7 +12526,7 @@ func (x *TcpRelayTarget) String() string { func (*TcpRelayTarget) ProtoMessage() {} func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[170] + mi := &file_openshell_proto_msgTypes[173] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12165,7 +12539,7 @@ func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use TcpRelayTarget.ProtoReflect.Descriptor instead. func (*TcpRelayTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{170} + return file_openshell_proto_rawDescGZIP(), []int{173} } func (x *TcpRelayTarget) GetHost() string { @@ -12193,7 +12567,7 @@ type RelayInit struct { func (x *RelayInit) Reset() { *x = RelayInit{} - mi := &file_openshell_proto_msgTypes[171] + mi := &file_openshell_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12205,7 +12579,7 @@ func (x *RelayInit) String() string { func (*RelayInit) ProtoMessage() {} func (x *RelayInit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[171] + mi := &file_openshell_proto_msgTypes[174] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12218,7 +12592,7 @@ func (x *RelayInit) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayInit.ProtoReflect.Descriptor instead. func (*RelayInit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{171} + return file_openshell_proto_rawDescGZIP(), []int{174} } func (x *RelayInit) GetChannelId() string { @@ -12245,7 +12619,7 @@ type RelayFrame struct { func (x *RelayFrame) Reset() { *x = RelayFrame{} - mi := &file_openshell_proto_msgTypes[172] + mi := &file_openshell_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12257,7 +12631,7 @@ func (x *RelayFrame) String() string { func (*RelayFrame) ProtoMessage() {} func (x *RelayFrame) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[172] + mi := &file_openshell_proto_msgTypes[175] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12270,7 +12644,7 @@ func (x *RelayFrame) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayFrame.ProtoReflect.Descriptor instead. func (*RelayFrame) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{172} + return file_openshell_proto_rawDescGZIP(), []int{175} } func (x *RelayFrame) GetPayload() isRelayFrame_Payload { @@ -12329,7 +12703,7 @@ type RelayOpenResult struct { func (x *RelayOpenResult) Reset() { *x = RelayOpenResult{} - mi := &file_openshell_proto_msgTypes[173] + mi := &file_openshell_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12341,7 +12715,7 @@ func (x *RelayOpenResult) String() string { func (*RelayOpenResult) ProtoMessage() {} func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[173] + mi := &file_openshell_proto_msgTypes[176] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12354,7 +12728,7 @@ func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpenResult.ProtoReflect.Descriptor instead. func (*RelayOpenResult) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{173} + return file_openshell_proto_rawDescGZIP(), []int{176} } func (x *RelayOpenResult) GetChannelId() string { @@ -12391,7 +12765,7 @@ type RelayClose struct { func (x *RelayClose) Reset() { *x = RelayClose{} - mi := &file_openshell_proto_msgTypes[174] + mi := &file_openshell_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12403,7 +12777,7 @@ func (x *RelayClose) String() string { func (*RelayClose) ProtoMessage() {} func (x *RelayClose) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[174] + mi := &file_openshell_proto_msgTypes[177] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12416,7 +12790,7 @@ func (x *RelayClose) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayClose.ProtoReflect.Descriptor instead. func (*RelayClose) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{174} + return file_openshell_proto_rawDescGZIP(), []int{177} } func (x *RelayClose) GetChannelId() string { @@ -12450,7 +12824,7 @@ type L7RequestSample struct { func (x *L7RequestSample) Reset() { *x = L7RequestSample{} - mi := &file_openshell_proto_msgTypes[175] + mi := &file_openshell_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12462,7 +12836,7 @@ func (x *L7RequestSample) String() string { func (*L7RequestSample) ProtoMessage() {} func (x *L7RequestSample) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[175] + mi := &file_openshell_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12475,7 +12849,7 @@ func (x *L7RequestSample) ProtoReflect() protoreflect.Message { // Deprecated: Use L7RequestSample.ProtoReflect.Descriptor instead. func (*L7RequestSample) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{175} + return file_openshell_proto_rawDescGZIP(), []int{178} } func (x *L7RequestSample) GetMethod() string { @@ -12549,7 +12923,7 @@ type DenialSummary struct { func (x *DenialSummary) Reset() { *x = DenialSummary{} - mi := &file_openshell_proto_msgTypes[176] + mi := &file_openshell_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12561,7 +12935,7 @@ func (x *DenialSummary) String() string { func (*DenialSummary) ProtoMessage() {} func (x *DenialSummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[176] + mi := &file_openshell_proto_msgTypes[179] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12574,7 +12948,7 @@ func (x *DenialSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialSummary.ProtoReflect.Descriptor instead. func (*DenialSummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{176} + return file_openshell_proto_rawDescGZIP(), []int{179} } func (x *DenialSummary) GetSandboxId() string { @@ -12709,7 +13083,7 @@ type DenialGroupCount struct { func (x *DenialGroupCount) Reset() { *x = DenialGroupCount{} - mi := &file_openshell_proto_msgTypes[177] + mi := &file_openshell_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12721,7 +13095,7 @@ func (x *DenialGroupCount) String() string { func (*DenialGroupCount) ProtoMessage() {} func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[177] + mi := &file_openshell_proto_msgTypes[180] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12734,7 +13108,7 @@ func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialGroupCount.ProtoReflect.Descriptor instead. func (*DenialGroupCount) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{177} + return file_openshell_proto_rawDescGZIP(), []int{180} } func (x *DenialGroupCount) GetDenyGroup() string { @@ -12767,7 +13141,7 @@ type NetworkActivitySummary struct { func (x *NetworkActivitySummary) Reset() { *x = NetworkActivitySummary{} - mi := &file_openshell_proto_msgTypes[178] + mi := &file_openshell_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12779,7 +13153,7 @@ func (x *NetworkActivitySummary) String() string { func (*NetworkActivitySummary) ProtoMessage() {} func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[178] + mi := &file_openshell_proto_msgTypes[181] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12792,7 +13166,7 @@ func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkActivitySummary.ProtoReflect.Descriptor instead. func (*NetworkActivitySummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{178} + return file_openshell_proto_rawDescGZIP(), []int{181} } func (x *NetworkActivitySummary) GetNetworkActivityCount() uint32 { @@ -12880,7 +13254,7 @@ type PolicyChunk struct { func (x *PolicyChunk) Reset() { *x = PolicyChunk{} - mi := &file_openshell_proto_msgTypes[179] + mi := &file_openshell_proto_msgTypes[182] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12892,7 +13266,7 @@ func (x *PolicyChunk) String() string { func (*PolicyChunk) ProtoMessage() {} func (x *PolicyChunk) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[179] + mi := &file_openshell_proto_msgTypes[182] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12905,7 +13279,7 @@ func (x *PolicyChunk) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyChunk.ProtoReflect.Descriptor instead. func (*PolicyChunk) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{179} + return file_openshell_proto_rawDescGZIP(), []int{182} } func (x *PolicyChunk) GetId() string { @@ -13093,7 +13467,7 @@ type DraftPolicyUpdate struct { func (x *DraftPolicyUpdate) Reset() { *x = DraftPolicyUpdate{} - mi := &file_openshell_proto_msgTypes[180] + mi := &file_openshell_proto_msgTypes[183] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13105,7 +13479,7 @@ func (x *DraftPolicyUpdate) String() string { func (*DraftPolicyUpdate) ProtoMessage() {} func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[180] + mi := &file_openshell_proto_msgTypes[183] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13118,7 +13492,7 @@ func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftPolicyUpdate.ProtoReflect.Descriptor instead. func (*DraftPolicyUpdate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{180} + return file_openshell_proto_rawDescGZIP(), []int{183} } func (x *DraftPolicyUpdate) GetDraftVersion() uint64 { @@ -13176,7 +13550,7 @@ type SubmitPolicyAnalysisRequest struct { func (x *SubmitPolicyAnalysisRequest) Reset() { *x = SubmitPolicyAnalysisRequest{} - mi := &file_openshell_proto_msgTypes[181] + mi := &file_openshell_proto_msgTypes[184] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13188,7 +13562,7 @@ func (x *SubmitPolicyAnalysisRequest) String() string { func (*SubmitPolicyAnalysisRequest) ProtoMessage() {} func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[181] + mi := &file_openshell_proto_msgTypes[184] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13201,7 +13575,7 @@ func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisRequest.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{181} + return file_openshell_proto_rawDescGZIP(), []int{184} } func (x *SubmitPolicyAnalysisRequest) GetSummaries() []*DenialSummary { @@ -13264,7 +13638,7 @@ type SubmitPolicyAnalysisResponse struct { func (x *SubmitPolicyAnalysisResponse) Reset() { *x = SubmitPolicyAnalysisResponse{} - mi := &file_openshell_proto_msgTypes[182] + mi := &file_openshell_proto_msgTypes[185] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13276,7 +13650,7 @@ func (x *SubmitPolicyAnalysisResponse) String() string { func (*SubmitPolicyAnalysisResponse) ProtoMessage() {} func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[182] + mi := &file_openshell_proto_msgTypes[185] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13289,7 +13663,7 @@ func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisResponse.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{182} + return file_openshell_proto_rawDescGZIP(), []int{185} } func (x *SubmitPolicyAnalysisResponse) GetAcceptedChunks() uint32 { @@ -13335,7 +13709,7 @@ type GetDraftPolicyRequest struct { func (x *GetDraftPolicyRequest) Reset() { *x = GetDraftPolicyRequest{} - mi := &file_openshell_proto_msgTypes[183] + mi := &file_openshell_proto_msgTypes[186] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13347,7 +13721,7 @@ func (x *GetDraftPolicyRequest) String() string { func (*GetDraftPolicyRequest) ProtoMessage() {} func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[183] + mi := &file_openshell_proto_msgTypes[186] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13360,7 +13734,7 @@ func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyRequest.ProtoReflect.Descriptor instead. func (*GetDraftPolicyRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{183} + return file_openshell_proto_rawDescGZIP(), []int{186} } func (x *GetDraftPolicyRequest) GetName() string { @@ -13400,7 +13774,7 @@ type GetDraftPolicyResponse struct { func (x *GetDraftPolicyResponse) Reset() { *x = GetDraftPolicyResponse{} - mi := &file_openshell_proto_msgTypes[184] + mi := &file_openshell_proto_msgTypes[187] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13412,7 +13786,7 @@ func (x *GetDraftPolicyResponse) String() string { func (*GetDraftPolicyResponse) ProtoMessage() {} func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[184] + mi := &file_openshell_proto_msgTypes[187] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13425,7 +13799,7 @@ func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyResponse.ProtoReflect.Descriptor instead. func (*GetDraftPolicyResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{184} + return file_openshell_proto_rawDescGZIP(), []int{187} } func (x *GetDraftPolicyResponse) GetChunks() []*PolicyChunk { @@ -13474,7 +13848,7 @@ type ApproveDraftChunkRequest struct { func (x *ApproveDraftChunkRequest) Reset() { *x = ApproveDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[185] + mi := &file_openshell_proto_msgTypes[188] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13486,7 +13860,7 @@ func (x *ApproveDraftChunkRequest) String() string { func (*ApproveDraftChunkRequest) ProtoMessage() {} func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[185] + mi := &file_openshell_proto_msgTypes[188] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13499,7 +13873,7 @@ func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkRequest.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{185} + return file_openshell_proto_rawDescGZIP(), []int{188} } func (x *ApproveDraftChunkRequest) GetName() string { @@ -13542,7 +13916,7 @@ type ApproveDraftChunkResponse struct { func (x *ApproveDraftChunkResponse) Reset() { *x = ApproveDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[186] + mi := &file_openshell_proto_msgTypes[189] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13554,7 +13928,7 @@ func (x *ApproveDraftChunkResponse) String() string { func (*ApproveDraftChunkResponse) ProtoMessage() {} func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[186] + mi := &file_openshell_proto_msgTypes[189] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13567,7 +13941,7 @@ func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkResponse.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{186} + return file_openshell_proto_rawDescGZIP(), []int{189} } func (x *ApproveDraftChunkResponse) GetPolicyVersion() uint32 { @@ -13601,7 +13975,7 @@ type RejectDraftChunkRequest struct { func (x *RejectDraftChunkRequest) Reset() { *x = RejectDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[187] + mi := &file_openshell_proto_msgTypes[190] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13613,7 +13987,7 @@ func (x *RejectDraftChunkRequest) String() string { func (*RejectDraftChunkRequest) ProtoMessage() {} func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[187] + mi := &file_openshell_proto_msgTypes[190] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13626,7 +14000,7 @@ func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkRequest.ProtoReflect.Descriptor instead. func (*RejectDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{187} + return file_openshell_proto_rawDescGZIP(), []int{190} } func (x *RejectDraftChunkRequest) GetName() string { @@ -13665,7 +14039,7 @@ type RejectDraftChunkResponse struct { func (x *RejectDraftChunkResponse) Reset() { *x = RejectDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[188] + mi := &file_openshell_proto_msgTypes[191] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13677,7 +14051,7 @@ func (x *RejectDraftChunkResponse) String() string { func (*RejectDraftChunkResponse) ProtoMessage() {} func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[188] + mi := &file_openshell_proto_msgTypes[191] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13690,7 +14064,7 @@ func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkResponse.ProtoReflect.Descriptor instead. func (*RejectDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{188} + return file_openshell_proto_rawDescGZIP(), []int{191} } // Approve all pending chunks. @@ -13704,7 +14078,7 @@ type DraftChunkApproval struct { func (x *DraftChunkApproval) Reset() { *x = DraftChunkApproval{} - mi := &file_openshell_proto_msgTypes[189] + mi := &file_openshell_proto_msgTypes[192] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13716,7 +14090,7 @@ func (x *DraftChunkApproval) String() string { func (*DraftChunkApproval) ProtoMessage() {} func (x *DraftChunkApproval) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[189] + mi := &file_openshell_proto_msgTypes[192] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13729,7 +14103,7 @@ func (x *DraftChunkApproval) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftChunkApproval.ProtoReflect.Descriptor instead. func (*DraftChunkApproval) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{189} + return file_openshell_proto_rawDescGZIP(), []int{192} } func (x *DraftChunkApproval) GetChunkId() string { @@ -13763,7 +14137,7 @@ type ApproveAllDraftChunksRequest struct { func (x *ApproveAllDraftChunksRequest) Reset() { *x = ApproveAllDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[190] + mi := &file_openshell_proto_msgTypes[193] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13775,7 +14149,7 @@ func (x *ApproveAllDraftChunksRequest) String() string { func (*ApproveAllDraftChunksRequest) ProtoMessage() {} func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[190] + mi := &file_openshell_proto_msgTypes[193] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13788,7 +14162,7 @@ func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{190} + return file_openshell_proto_rawDescGZIP(), []int{193} } func (x *ApproveAllDraftChunksRequest) GetName() string { @@ -13836,7 +14210,7 @@ type ApproveAllDraftChunksResponse struct { func (x *ApproveAllDraftChunksResponse) Reset() { *x = ApproveAllDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[191] + mi := &file_openshell_proto_msgTypes[194] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13848,7 +14222,7 @@ func (x *ApproveAllDraftChunksResponse) String() string { func (*ApproveAllDraftChunksResponse) ProtoMessage() {} func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[191] + mi := &file_openshell_proto_msgTypes[194] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13861,7 +14235,7 @@ func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{191} + return file_openshell_proto_rawDescGZIP(), []int{194} } func (x *ApproveAllDraftChunksResponse) GetPolicyVersion() uint32 { @@ -13909,7 +14283,7 @@ type EditDraftChunkRequest struct { func (x *EditDraftChunkRequest) Reset() { *x = EditDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[192] + mi := &file_openshell_proto_msgTypes[195] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13921,7 +14295,7 @@ func (x *EditDraftChunkRequest) String() string { func (*EditDraftChunkRequest) ProtoMessage() {} func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[192] + mi := &file_openshell_proto_msgTypes[195] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13934,7 +14308,7 @@ func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkRequest.ProtoReflect.Descriptor instead. func (*EditDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{192} + return file_openshell_proto_rawDescGZIP(), []int{195} } func (x *EditDraftChunkRequest) GetName() string { @@ -13973,7 +14347,7 @@ type EditDraftChunkResponse struct { func (x *EditDraftChunkResponse) Reset() { *x = EditDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[193] + mi := &file_openshell_proto_msgTypes[196] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13985,7 +14359,7 @@ func (x *EditDraftChunkResponse) String() string { func (*EditDraftChunkResponse) ProtoMessage() {} func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[193] + mi := &file_openshell_proto_msgTypes[196] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13998,7 +14372,7 @@ func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkResponse.ProtoReflect.Descriptor instead. func (*EditDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{193} + return file_openshell_proto_rawDescGZIP(), []int{196} } // Reverse an approval (remove merged rule from active policy). @@ -14016,7 +14390,7 @@ type UndoDraftChunkRequest struct { func (x *UndoDraftChunkRequest) Reset() { *x = UndoDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[194] + mi := &file_openshell_proto_msgTypes[197] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14028,7 +14402,7 @@ func (x *UndoDraftChunkRequest) String() string { func (*UndoDraftChunkRequest) ProtoMessage() {} func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[194] + mi := &file_openshell_proto_msgTypes[197] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14041,7 +14415,7 @@ func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkRequest.ProtoReflect.Descriptor instead. func (*UndoDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{194} + return file_openshell_proto_rawDescGZIP(), []int{197} } func (x *UndoDraftChunkRequest) GetName() string { @@ -14077,7 +14451,7 @@ type UndoDraftChunkResponse struct { func (x *UndoDraftChunkResponse) Reset() { *x = UndoDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[195] + mi := &file_openshell_proto_msgTypes[198] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14089,7 +14463,7 @@ func (x *UndoDraftChunkResponse) String() string { func (*UndoDraftChunkResponse) ProtoMessage() {} func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[195] + mi := &file_openshell_proto_msgTypes[198] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14102,7 +14476,7 @@ func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkResponse.ProtoReflect.Descriptor instead. func (*UndoDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{195} + return file_openshell_proto_rawDescGZIP(), []int{198} } func (x *UndoDraftChunkResponse) GetPolicyVersion() uint32 { @@ -14132,7 +14506,7 @@ type ClearDraftChunksRequest struct { func (x *ClearDraftChunksRequest) Reset() { *x = ClearDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[196] + mi := &file_openshell_proto_msgTypes[199] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14144,7 +14518,7 @@ func (x *ClearDraftChunksRequest) String() string { func (*ClearDraftChunksRequest) ProtoMessage() {} func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[196] + mi := &file_openshell_proto_msgTypes[199] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14157,7 +14531,7 @@ func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ClearDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{196} + return file_openshell_proto_rawDescGZIP(), []int{199} } func (x *ClearDraftChunksRequest) GetName() string { @@ -14184,7 +14558,7 @@ type ClearDraftChunksResponse struct { func (x *ClearDraftChunksResponse) Reset() { *x = ClearDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[197] + mi := &file_openshell_proto_msgTypes[200] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14196,7 +14570,7 @@ func (x *ClearDraftChunksResponse) String() string { func (*ClearDraftChunksResponse) ProtoMessage() {} func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[197] + mi := &file_openshell_proto_msgTypes[200] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14209,7 +14583,7 @@ func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ClearDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{197} + return file_openshell_proto_rawDescGZIP(), []int{200} } func (x *ClearDraftChunksResponse) GetChunksCleared() uint32 { @@ -14232,7 +14606,7 @@ type GetDraftHistoryRequest struct { func (x *GetDraftHistoryRequest) Reset() { *x = GetDraftHistoryRequest{} - mi := &file_openshell_proto_msgTypes[198] + mi := &file_openshell_proto_msgTypes[201] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14244,7 +14618,7 @@ func (x *GetDraftHistoryRequest) String() string { func (*GetDraftHistoryRequest) ProtoMessage() {} func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[198] + mi := &file_openshell_proto_msgTypes[201] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14257,7 +14631,7 @@ func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryRequest.ProtoReflect.Descriptor instead. func (*GetDraftHistoryRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{198} + return file_openshell_proto_rawDescGZIP(), []int{201} } func (x *GetDraftHistoryRequest) GetName() string { @@ -14291,7 +14665,7 @@ type DraftHistoryEntry struct { func (x *DraftHistoryEntry) Reset() { *x = DraftHistoryEntry{} - mi := &file_openshell_proto_msgTypes[199] + mi := &file_openshell_proto_msgTypes[202] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14303,7 +14677,7 @@ func (x *DraftHistoryEntry) String() string { func (*DraftHistoryEntry) ProtoMessage() {} func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[199] + mi := &file_openshell_proto_msgTypes[202] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14316,7 +14690,7 @@ func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftHistoryEntry.ProtoReflect.Descriptor instead. func (*DraftHistoryEntry) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{199} + return file_openshell_proto_rawDescGZIP(), []int{202} } func (x *DraftHistoryEntry) GetTimestampMs() int64 { @@ -14357,7 +14731,7 @@ type GetDraftHistoryResponse struct { func (x *GetDraftHistoryResponse) Reset() { *x = GetDraftHistoryResponse{} - mi := &file_openshell_proto_msgTypes[200] + mi := &file_openshell_proto_msgTypes[203] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14369,7 +14743,7 @@ func (x *GetDraftHistoryResponse) String() string { func (*GetDraftHistoryResponse) ProtoMessage() {} func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[200] + mi := &file_openshell_proto_msgTypes[203] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14382,7 +14756,7 @@ func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryResponse.ProtoReflect.Descriptor instead. func (*GetDraftHistoryResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{200} + return file_openshell_proto_rawDescGZIP(), []int{203} } func (x *GetDraftHistoryResponse) GetEntries() []*DraftHistoryEntry { @@ -14405,7 +14779,7 @@ type CreateWorkspaceRequest struct { func (x *CreateWorkspaceRequest) Reset() { *x = CreateWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[201] + mi := &file_openshell_proto_msgTypes[204] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14417,7 +14791,7 @@ func (x *CreateWorkspaceRequest) String() string { func (*CreateWorkspaceRequest) ProtoMessage() {} func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[201] + mi := &file_openshell_proto_msgTypes[204] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14430,7 +14804,7 @@ func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceRequest.ProtoReflect.Descriptor instead. func (*CreateWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{201} + return file_openshell_proto_rawDescGZIP(), []int{204} } func (x *CreateWorkspaceRequest) GetName() string { @@ -14457,7 +14831,7 @@ type CreateWorkspaceResponse struct { func (x *CreateWorkspaceResponse) Reset() { *x = CreateWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[202] + mi := &file_openshell_proto_msgTypes[205] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14469,7 +14843,7 @@ func (x *CreateWorkspaceResponse) String() string { func (*CreateWorkspaceResponse) ProtoMessage() {} func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[202] + mi := &file_openshell_proto_msgTypes[205] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14482,7 +14856,7 @@ func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceResponse.ProtoReflect.Descriptor instead. func (*CreateWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{202} + return file_openshell_proto_rawDescGZIP(), []int{205} } func (x *CreateWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -14503,7 +14877,7 @@ type GetWorkspaceRequest struct { func (x *GetWorkspaceRequest) Reset() { *x = GetWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[203] + mi := &file_openshell_proto_msgTypes[206] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14515,7 +14889,7 @@ func (x *GetWorkspaceRequest) String() string { func (*GetWorkspaceRequest) ProtoMessage() {} func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[203] + mi := &file_openshell_proto_msgTypes[206] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14528,7 +14902,7 @@ func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceRequest.ProtoReflect.Descriptor instead. func (*GetWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{203} + return file_openshell_proto_rawDescGZIP(), []int{206} } func (x *GetWorkspaceRequest) GetName() string { @@ -14548,7 +14922,7 @@ type GetWorkspaceResponse struct { func (x *GetWorkspaceResponse) Reset() { *x = GetWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[204] + mi := &file_openshell_proto_msgTypes[207] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14560,7 +14934,7 @@ func (x *GetWorkspaceResponse) String() string { func (*GetWorkspaceResponse) ProtoMessage() {} func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[204] + mi := &file_openshell_proto_msgTypes[207] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14573,7 +14947,7 @@ func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceResponse.ProtoReflect.Descriptor instead. func (*GetWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{204} + return file_openshell_proto_rawDescGZIP(), []int{207} } func (x *GetWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -14600,7 +14974,7 @@ type ListWorkspacesRequest struct { func (x *ListWorkspacesRequest) Reset() { *x = ListWorkspacesRequest{} - mi := &file_openshell_proto_msgTypes[205] + mi := &file_openshell_proto_msgTypes[208] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14612,7 +14986,7 @@ func (x *ListWorkspacesRequest) String() string { func (*ListWorkspacesRequest) ProtoMessage() {} func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[205] + mi := &file_openshell_proto_msgTypes[208] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14625,7 +14999,7 @@ func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesRequest.ProtoReflect.Descriptor instead. func (*ListWorkspacesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{205} + return file_openshell_proto_rawDescGZIP(), []int{208} } func (x *ListWorkspacesRequest) GetPageSize() int32 { @@ -14661,7 +15035,7 @@ type ListWorkspacesResponse struct { func (x *ListWorkspacesResponse) Reset() { *x = ListWorkspacesResponse{} - mi := &file_openshell_proto_msgTypes[206] + mi := &file_openshell_proto_msgTypes[209] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14673,7 +15047,7 @@ func (x *ListWorkspacesResponse) String() string { func (*ListWorkspacesResponse) ProtoMessage() {} func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[206] + mi := &file_openshell_proto_msgTypes[209] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14686,7 +15060,7 @@ func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesResponse.ProtoReflect.Descriptor instead. func (*ListWorkspacesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{206} + return file_openshell_proto_rawDescGZIP(), []int{209} } func (x *ListWorkspacesResponse) GetWorkspaces() []*datamodelv1.Workspace { @@ -14714,7 +15088,7 @@ type DeleteWorkspaceRequest struct { func (x *DeleteWorkspaceRequest) Reset() { *x = DeleteWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[207] + mi := &file_openshell_proto_msgTypes[210] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14726,7 +15100,7 @@ func (x *DeleteWorkspaceRequest) String() string { func (*DeleteWorkspaceRequest) ProtoMessage() {} func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[207] + mi := &file_openshell_proto_msgTypes[210] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14739,7 +15113,7 @@ func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceRequest.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{207} + return file_openshell_proto_rawDescGZIP(), []int{210} } func (x *DeleteWorkspaceRequest) GetName() string { @@ -14759,7 +15133,7 @@ type DeleteWorkspaceResponse struct { func (x *DeleteWorkspaceResponse) Reset() { *x = DeleteWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[208] + mi := &file_openshell_proto_msgTypes[211] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14771,7 +15145,7 @@ func (x *DeleteWorkspaceResponse) String() string { func (*DeleteWorkspaceResponse) ProtoMessage() {} func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[208] + mi := &file_openshell_proto_msgTypes[211] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14784,7 +15158,7 @@ func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceResponse.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{208} + return file_openshell_proto_rawDescGZIP(), []int{211} } func (x *DeleteWorkspaceResponse) GetDeleted() bool { @@ -14808,7 +15182,7 @@ type WorkspaceMember struct { func (x *WorkspaceMember) Reset() { *x = WorkspaceMember{} - mi := &file_openshell_proto_msgTypes[209] + mi := &file_openshell_proto_msgTypes[212] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14820,7 +15194,7 @@ func (x *WorkspaceMember) String() string { func (*WorkspaceMember) ProtoMessage() {} func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[209] + mi := &file_openshell_proto_msgTypes[212] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14833,7 +15207,7 @@ func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspaceMember.ProtoReflect.Descriptor instead. func (*WorkspaceMember) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{209} + return file_openshell_proto_rawDescGZIP(), []int{212} } func (x *WorkspaceMember) GetMetadata() *datamodelv1.ObjectMeta { @@ -14872,7 +15246,7 @@ type AddWorkspaceMemberRequest struct { func (x *AddWorkspaceMemberRequest) Reset() { *x = AddWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[210] + mi := &file_openshell_proto_msgTypes[213] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14884,7 +15258,7 @@ func (x *AddWorkspaceMemberRequest) String() string { func (*AddWorkspaceMemberRequest) ProtoMessage() {} func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[210] + mi := &file_openshell_proto_msgTypes[213] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14897,7 +15271,7 @@ func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{210} + return file_openshell_proto_rawDescGZIP(), []int{213} } func (x *AddWorkspaceMemberRequest) GetWorkspace() string { @@ -14931,7 +15305,7 @@ type AddWorkspaceMemberResponse struct { func (x *AddWorkspaceMemberResponse) Reset() { *x = AddWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[211] + mi := &file_openshell_proto_msgTypes[214] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14943,7 +15317,7 @@ func (x *AddWorkspaceMemberResponse) String() string { func (*AddWorkspaceMemberResponse) ProtoMessage() {} func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[211] + mi := &file_openshell_proto_msgTypes[214] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14956,7 +15330,7 @@ func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{211} + return file_openshell_proto_rawDescGZIP(), []int{214} } func (x *AddWorkspaceMemberResponse) GetMember() *WorkspaceMember { @@ -14979,7 +15353,7 @@ type RemoveWorkspaceMemberRequest struct { func (x *RemoveWorkspaceMemberRequest) Reset() { *x = RemoveWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[212] + mi := &file_openshell_proto_msgTypes[215] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14991,7 +15365,7 @@ func (x *RemoveWorkspaceMemberRequest) String() string { func (*RemoveWorkspaceMemberRequest) ProtoMessage() {} func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[212] + mi := &file_openshell_proto_msgTypes[215] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15004,7 +15378,7 @@ func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{212} + return file_openshell_proto_rawDescGZIP(), []int{215} } func (x *RemoveWorkspaceMemberRequest) GetWorkspace() string { @@ -15031,7 +15405,7 @@ type RemoveWorkspaceMemberResponse struct { func (x *RemoveWorkspaceMemberResponse) Reset() { *x = RemoveWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[213] + mi := &file_openshell_proto_msgTypes[216] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15043,7 +15417,7 @@ func (x *RemoveWorkspaceMemberResponse) String() string { func (*RemoveWorkspaceMemberResponse) ProtoMessage() {} func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[213] + mi := &file_openshell_proto_msgTypes[216] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15056,7 +15430,7 @@ func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{213} + return file_openshell_proto_rawDescGZIP(), []int{216} } func (x *RemoveWorkspaceMemberResponse) GetRemoved() bool { @@ -15083,7 +15457,7 @@ type ListWorkspaceMembersRequest struct { func (x *ListWorkspaceMembersRequest) Reset() { *x = ListWorkspaceMembersRequest{} - mi := &file_openshell_proto_msgTypes[214] + mi := &file_openshell_proto_msgTypes[217] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15095,7 +15469,7 @@ func (x *ListWorkspaceMembersRequest) String() string { func (*ListWorkspaceMembersRequest) ProtoMessage() {} func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[214] + mi := &file_openshell_proto_msgTypes[217] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15108,7 +15482,7 @@ func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersRequest.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{214} + return file_openshell_proto_rawDescGZIP(), []int{217} } func (x *ListWorkspaceMembersRequest) GetWorkspace() string { @@ -15144,7 +15518,7 @@ type ListWorkspaceMembersResponse struct { func (x *ListWorkspaceMembersResponse) Reset() { *x = ListWorkspaceMembersResponse{} - mi := &file_openshell_proto_msgTypes[215] + mi := &file_openshell_proto_msgTypes[218] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15156,7 +15530,7 @@ func (x *ListWorkspaceMembersResponse) String() string { func (*ListWorkspaceMembersResponse) ProtoMessage() {} func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[215] + mi := &file_openshell_proto_msgTypes[218] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15169,7 +15543,7 @@ func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersResponse.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{215} + return file_openshell_proto_rawDescGZIP(), []int{218} } func (x *ListWorkspaceMembersResponse) GetMembers() []*WorkspaceMember { @@ -15204,7 +15578,7 @@ type ExtensionServiceCredential struct { func (x *ExtensionServiceCredential) Reset() { *x = ExtensionServiceCredential{} - mi := &file_openshell_proto_msgTypes[216] + mi := &file_openshell_proto_msgTypes[219] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15216,7 +15590,7 @@ func (x *ExtensionServiceCredential) String() string { func (*ExtensionServiceCredential) ProtoMessage() {} func (x *ExtensionServiceCredential) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[216] + mi := &file_openshell_proto_msgTypes[219] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15229,7 +15603,7 @@ func (x *ExtensionServiceCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use ExtensionServiceCredential.ProtoReflect.Descriptor instead. func (*ExtensionServiceCredential) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{216} + return file_openshell_proto_rawDescGZIP(), []int{219} } func (x *ExtensionServiceCredential) GetServiceName() string { @@ -15908,7 +16282,7 @@ const file_openshell_proto_rawDesc = "" + "\n" + "expires_in\x18\x02 \x01(\x03R\texpiresIn\x12\x1d\n" + "\n" + - "token_type\x18\x03 \x01(\tR\ttokenType\"\x95\x05\n" + + "token_type\x18\x03 \x01(\tR\ttokenType\"\xb3\x06\n" + "\x13UpdateConfigRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12;\n" + "\x06policy\x18\x02 \x01(\v2#.openshell.sandbox.v1.SandboxPolicyR\x06policy\x12\x1f\n" + @@ -15920,11 +16294,32 @@ const file_openshell_proto_rawDesc = "" + "\x10merge_operations\x18\a \x03(\v2\".openshell.v1.PolicyMergeOperationR\x0fmergeOperations\x12:\n" + "\x19expected_resource_version\x18\b \x01(\x04R\x17expectedResourceVersion\x12T\n" + "\vannotations\x18\t \x03(\v22.openshell.v1.UpdateConfigRequest.AnnotationsEntryR\vannotations\x12R\n" + - "\x0fworkspace_scope\x18\v \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x1a>\n" + + "\x0fworkspace_scope\x18\v \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x12G\n" + + "\vconsistency\x18\f \x01(\x0e2%.openshell.v1.ConfigUpdateConsistencyR\vconsistency\x12'\n" + + "\x0fidempotency_key\x18\r \x01(\tR\x0eidempotencyKey\x12*\n" + + "\x11wait_timeout_secs\x18\x0e \x01(\rR\x0fwaitTimeoutSecs\x1a>\n" + "\x10AnnotationsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\n" + - "\x10\vR\tworkspace\"\xc7\x03\n" + + "\x10\vR\tworkspace\"\xfa\x03\n" + + "\x15ConfigUpdateOperation\x12!\n" + + "\foperation_id\x18\x01 \x01(\tR\voperationId\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x02 \x01(\tR\tsandboxId\x12;\n" + + "\tcomponent\x18\x03 \x01(\x0e2\x1d.openshell.v1.ConfigComponentR\tcomponent\x12M\n" + + "\x0ftarget_revision\x18\x04 \x01(\v2$.openshell.v1.ConfigSnapshotRevisionR\x0etargetRevision\x12>\n" + + "\x05state\x18\x05 \x01(\x0e2(.openshell.v1.ConfigUpdateOperationStateR\x05state\x12:\n" + + "\aoutcome\x18\x06 \x01(\x0e2 .openshell.v1.ConfigApplyOutcomeR\aoutcome\x12'\n" + + "\x0fsanitized_error\x18\a \x01(\tR\x0esanitizedError\x12\"\n" + + "\rcreated_at_ms\x18\b \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\t \x01(\x03R\vupdatedAtMs\x12&\n" + + "\x0fcompleted_at_ms\x18\n" + + " \x01(\x03R\rcompletedAtMs\"\x98\x01\n" + + "\x1fGetConfigUpdateOperationRequest\x12!\n" + + "\foperation_id\x18\x01 \x01(\tR\voperationId\x12R\n" + + "\x0fworkspace_scope\x18\x02 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\"e\n" + + " GetConfigUpdateOperationResponse\x12A\n" + + "\toperation\x18\x01 \x01(\v2#.openshell.v1.ConfigUpdateOperationR\toperation\"\xc7\x03\n" + "\x14PolicyMergeOperation\x129\n" + "\badd_rule\x18\x01 \x01(\v2\x1c.openshell.v1.AddNetworkRuleH\x00R\aaddRule\x12N\n" + "\x0fremove_endpoint\x18\x02 \x01(\v2#.openshell.v1.RemoveNetworkEndpointH\x00R\x0eremoveEndpoint\x12B\n" + @@ -15955,14 +16350,15 @@ const file_openshell_proto_rawDesc = "" + "\x13RemoveNetworkBinary\x12\x1b\n" + "\trule_name\x18\x01 \x01(\tR\bruleName\x12\x1f\n" + "\vbinary_path\x18\x02 \x01(\tR\n" + - "binaryPath\"\xaf\x02\n" + + "binaryPath\"\xf2\x02\n" + "\x14UpdateConfigResponse\x12\x18\n" + "\aversion\x18\x01 \x01(\rR\aversion\x12\x1f\n" + "\vpolicy_hash\x18\x02 \x01(\tR\n" + "policyHash\x12+\n" + "\x11settings_revision\x18\x03 \x01(\x04R\x10settingsRevision\x12\x18\n" + "\adeleted\x18\x04 \x01(\bR\adeleted\x12U\n" + - "\vannotations\x18\x05 \x03(\v23.openshell.v1.UpdateConfigResponse.AnnotationsEntryR\vannotations\x1a>\n" + + "\vannotations\x18\x05 \x03(\v23.openshell.v1.UpdateConfigResponse.AnnotationsEntryR\vannotations\x12A\n" + + "\toperation\x18\x06 \x01(\v2#.openshell.v1.ConfigUpdateOperationR\toperation\x1a>\n" + "\x10AnnotationsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xca\x01\n" + @@ -16068,12 +16464,13 @@ const file_openshell_proto_rawDesc = "" + "\x16ConfigSnapshotRevision\x12L\n" + "\x0esandbox_config\x18\x01 \x01(\v2#.openshell.v1.SandboxConfigRevisionH\x00R\rsandboxConfig\x123\n" + "\x14provider_environment\x18\x02 \x01(\x04H\x00R\x13providerEnvironmentB\v\n" + - "\tcomponent\"\xe4\x01\n" + + "\tcomponent\"\x91\x02\n" + "\x15SandboxConfigRevision\x12'\n" + "\x0fconfig_revision\x18\x01 \x01(\x04R\x0econfigRevision\x12%\n" + "\x0epolicy_version\x18\x02 \x01(\rR\rpolicyVersion\x12G\n" + "\rpolicy_source\x18\x03 \x01(\x0e2\".openshell.sandbox.v1.PolicySourceR\fpolicySource\x122\n" + - "\x15global_policy_version\x18\x04 \x01(\rR\x13globalPolicyVersion\"`\n" + + "\x15global_policy_version\x18\x04 \x01(\rR\x13globalPolicyVersion\x12+\n" + + "\x11settings_revision\x18\x05 \x01(\x04R\x10settingsRevision\"`\n" + "\x12ConfigApplyFailure\x12\x12\n" + "\x04code\x18\x01 \x01(\tR\x04code\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x12\x1c\n" + @@ -16378,7 +16775,19 @@ const file_openshell_proto_rawDesc = "" + "&ProviderEnvironmentValueClassification\x129\n" + "5PROVIDER_ENVIRONMENT_VALUE_CLASSIFICATION_UNSPECIFIED\x10\x00\x128\n" + "4PROVIDER_ENVIRONMENT_VALUE_CLASSIFICATION_NON_SECRET\x10\x01\x12?\n" + - ";PROVIDER_ENVIRONMENT_VALUE_CLASSIFICATION_STATIC_CREDENTIAL\x10\x02*\x9a\x01\n" + + ";PROVIDER_ENVIRONMENT_VALUE_CLASSIFICATION_STATIC_CREDENTIAL\x10\x02*\x9d\x01\n" + + "\x17ConfigUpdateConsistency\x12)\n" + + "%CONFIG_UPDATE_CONSISTENCY_UNSPECIFIED\x10\x00\x12)\n" + + "%CONFIG_UPDATE_CONSISTENCY_COMMIT_ONLY\x10\x01\x12,\n" + + "(CONFIG_UPDATE_CONSISTENCY_WAIT_FOR_APPLY\x10\x02*\xd2\x02\n" + + "\x1aConfigUpdateOperationState\x12-\n" + + ")CONFIG_UPDATE_OPERATION_STATE_UNSPECIFIED\x10\x00\x12)\n" + + "%CONFIG_UPDATE_OPERATION_STATE_PENDING\x10\x01\x12)\n" + + "%CONFIG_UPDATE_OPERATION_STATE_APPLIED\x10\x02\x12*\n" + + "&CONFIG_UPDATE_OPERATION_STATE_INACTIVE\x10\x03\x12(\n" + + "$CONFIG_UPDATE_OPERATION_STATE_FAILED\x10\x04\x12,\n" + + "(CONFIG_UPDATE_OPERATION_STATE_SUPERSEDED\x10\x05\x12+\n" + + "'CONFIG_UPDATE_OPERATION_STATE_CANCELLED\x10\x06*\x9a\x01\n" + "\fPolicyStatus\x12\x1d\n" + "\x19POLICY_STATUS_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15POLICY_STATUS_PENDING\x10\x01\x12\x18\n" + @@ -16413,7 +16822,7 @@ const file_openshell_proto_rawDesc = "" + "1PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_RETRY\x10\x01\x12;\n" + "7PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_REAUTHORIZE\x10\x02\x12A\n" + "=PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_FIX_CONFIGURATION\x10\x03\x12;\n" + - "7PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_INVESTIGATE\x10\x042\x8dM\n" + + "7PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_INVESTIGATE\x10\x042\x91M\n" + "\tOpenShell\x12Z\n" + "\x06Health\x12\x1b.openshell.v1.HealthRequest\x1a\x1c.openshell.v1.HealthResponse\"\x15\x82\xb5\x18\x11\n" + "\x0funauthenticated\x12i\n" + @@ -16505,14 +16914,14 @@ const file_openshell_proto_rawDesc = "" + "\x10GetGatewayConfig\x12-.openshell.sandbox.v1.GetGatewayConfigRequest\x1a..openshell.sandbox.v1.GetGatewayConfigResponse\"\x19\x82\xb5\x18\x15\n" + "\x06bearer\"\vconfig:read\x12v\n" + "\fUpdateConfig\x12!.openshell.v1.UpdateConfigRequest\x1a\".openshell.v1.UpdateConfigResponse\"\x1f\x82\xb5\x18\x1b\n" + - "\x04dual\x12\x05admin\"\fconfig:write\x12\x95\x01\n" + + "\x04dual\x12\x05admin\"\fconfig:write\x12\x9b\x01\n" + + "\x18GetConfigUpdateOperation\x12-.openshell.v1.GetConfigUpdateOperationRequest\x1a..openshell.v1.GetConfigUpdateOperationResponse\" \x82\xb5\x18\x1c\n" + + "\x06bearer\x12\x04user\"\fsandbox:read\x12\x95\x01\n" + "\x16GetSandboxPolicyStatus\x12+.openshell.v1.GetSandboxPolicyStatusRequest\x1a,.openshell.v1.GetSandboxPolicyStatusResponse\" \x82\xb5\x18\x1c\n" + "\x06bearer\x12\x04user\"\fsandbox:read\x12\x8c\x01\n" + "\x13ListSandboxPolicies\x12(.openshell.v1.ListSandboxPoliciesRequest\x1a).openshell.v1.ListSandboxPoliciesResponse\" \x82\xb5\x18\x1c\n" + "\x06bearer\x12\x04user\"\fsandbox:read\x12v\n" + "\x12ReportPolicyStatus\x12'.openshell.v1.ReportPolicyStatusRequest\x1a(.openshell.v1.ReportPolicyStatusResponse\"\r\x82\xb5\x18\t\n" + - "\asandbox\x12\x97\x01\n" + - "\x1dGetSandboxProviderEnvironment\x122.openshell.v1.GetSandboxProviderEnvironmentRequest\x1a3.openshell.v1.GetSandboxProviderEnvironmentResponse\"\r\x82\xb5\x18\t\n" + "\asandbox\x12\x94\x01\n" + "\x1cExchangeProviderSubjectToken\x121.openshell.v1.ExchangeProviderSubjectTokenRequest\x1a2.openshell.v1.ExchangeProviderSubjectTokenResponse\"\r\x82\xb5\x18\t\n" + "\asandbox\x12}\n" + @@ -16579,659 +16988,672 @@ func file_openshell_proto_rawDescGZIP() []byte { return file_openshell_proto_rawDescData } -var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 11) -var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 239) +var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 13) +var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 242) var file_openshell_proto_goTypes = []any{ (SandboxPhase)(0), // 0: openshell.v1.SandboxPhase (ProviderCredentialTokenGrantType)(0), // 1: openshell.v1.ProviderCredentialTokenGrantType (ProviderCredentialRefreshStrategy)(0), // 2: openshell.v1.ProviderCredentialRefreshStrategy (ProviderProfileCategory)(0), // 3: openshell.v1.ProviderProfileCategory (ProviderEnvironmentValueClassification)(0), // 4: openshell.v1.ProviderEnvironmentValueClassification - (PolicyStatus)(0), // 5: openshell.v1.PolicyStatus - (ConfigComponent)(0), // 6: openshell.v1.ConfigComponent - (ConfigApplyOutcome)(0), // 7: openshell.v1.ConfigApplyOutcome - (ServiceStatus)(0), // 8: openshell.v1.ServiceStatus - (WorkspaceRole)(0), // 9: openshell.v1.WorkspaceRole - (ProviderCredentialRefreshRecoveryAction)(0), // 10: openshell.v1.ProviderCredentialRefreshRecoveryAction - (*IssueSandboxTokenRequest)(nil), // 11: openshell.v1.IssueSandboxTokenRequest - (*IssueSandboxTokenResponse)(nil), // 12: openshell.v1.IssueSandboxTokenResponse - (*RefreshSandboxTokenRequest)(nil), // 13: openshell.v1.RefreshSandboxTokenRequest - (*RefreshSandboxTokenResponse)(nil), // 14: openshell.v1.RefreshSandboxTokenResponse - (*HealthRequest)(nil), // 15: openshell.v1.HealthRequest - (*HealthResponse)(nil), // 16: openshell.v1.HealthResponse - (*GetCurrentUserRequest)(nil), // 17: openshell.v1.GetCurrentUserRequest - (*GetCurrentUserResponse)(nil), // 18: openshell.v1.GetCurrentUserResponse - (*GetGatewayInfoRequest)(nil), // 19: openshell.v1.GetGatewayInfoRequest - (*GetGatewayInfoResponse)(nil), // 20: openshell.v1.GetGatewayInfoResponse - (*ComputeDriverInfo)(nil), // 21: openshell.v1.ComputeDriverInfo - (*ComputeDriverCapabilities)(nil), // 22: openshell.v1.ComputeDriverCapabilities - (*ResourceCapabilities)(nil), // 23: openshell.v1.ResourceCapabilities - (*CpuResourceCapabilities)(nil), // 24: openshell.v1.CpuResourceCapabilities - (*MemoryResourceCapabilities)(nil), // 25: openshell.v1.MemoryResourceCapabilities - (*GpuResourceCapabilities)(nil), // 26: openshell.v1.GpuResourceCapabilities - (*Sandbox)(nil), // 27: openshell.v1.Sandbox - (*SandboxSpec)(nil), // 28: openshell.v1.SandboxSpec - (*ResourceRequirements)(nil), // 29: openshell.v1.ResourceRequirements - (*GpuResourceRequirements)(nil), // 30: openshell.v1.GpuResourceRequirements - (*SandboxTemplate)(nil), // 31: openshell.v1.SandboxTemplate - (*SandboxWorkloadTemplate)(nil), // 32: openshell.v1.SandboxWorkloadTemplate - (*SandboxWorkloadTemplateSpec)(nil), // 33: openshell.v1.SandboxWorkloadTemplateSpec - (*SandboxWorkloadConfig)(nil), // 34: openshell.v1.SandboxWorkloadConfig - (*SandboxResources)(nil), // 35: openshell.v1.SandboxResources - (*SandboxServiceLevel)(nil), // 36: openshell.v1.SandboxServiceLevel - (*SandboxStartup)(nil), // 37: openshell.v1.SandboxStartup - (*SandboxWorkloadTemplateProvenance)(nil), // 38: openshell.v1.SandboxWorkloadTemplateProvenance - (*SandboxStatus)(nil), // 39: openshell.v1.SandboxStatus - (*SandboxCondition)(nil), // 40: openshell.v1.SandboxCondition - (*PlatformEvent)(nil), // 41: openshell.v1.PlatformEvent - (*CreateSandboxRequest)(nil), // 42: openshell.v1.CreateSandboxRequest - (*CreateSandboxTemplateRequest)(nil), // 43: openshell.v1.CreateSandboxTemplateRequest - (*GetSandboxTemplateRequest)(nil), // 44: openshell.v1.GetSandboxTemplateRequest - (*ListSandboxTemplatesRequest)(nil), // 45: openshell.v1.ListSandboxTemplatesRequest - (*DeleteSandboxTemplateRequest)(nil), // 46: openshell.v1.DeleteSandboxTemplateRequest - (*SandboxTemplateResponse)(nil), // 47: openshell.v1.SandboxTemplateResponse - (*ListSandboxTemplatesResponse)(nil), // 48: openshell.v1.ListSandboxTemplatesResponse - (*DeleteSandboxTemplateResponse)(nil), // 49: openshell.v1.DeleteSandboxTemplateResponse - (*BeginRootfsTarStagingRequest)(nil), // 50: openshell.v1.BeginRootfsTarStagingRequest - (*BeginRootfsTarStagingResponse)(nil), // 51: openshell.v1.BeginRootfsTarStagingResponse - (*GetSandboxRequest)(nil), // 52: openshell.v1.GetSandboxRequest - (*ListSandboxesRequest)(nil), // 53: openshell.v1.ListSandboxesRequest - (*ListSandboxProvidersRequest)(nil), // 54: openshell.v1.ListSandboxProvidersRequest - (*AttachSandboxProviderRequest)(nil), // 55: openshell.v1.AttachSandboxProviderRequest - (*DetachSandboxProviderRequest)(nil), // 56: openshell.v1.DetachSandboxProviderRequest - (*DeleteSandboxRequest)(nil), // 57: openshell.v1.DeleteSandboxRequest - (*StopSandboxRequest)(nil), // 58: openshell.v1.StopSandboxRequest - (*StartSandboxRequest)(nil), // 59: openshell.v1.StartSandboxRequest - (*SandboxResponse)(nil), // 60: openshell.v1.SandboxResponse - (*ListSandboxesResponse)(nil), // 61: openshell.v1.ListSandboxesResponse - (*ListSandboxProvidersResponse)(nil), // 62: openshell.v1.ListSandboxProvidersResponse - (*AttachSandboxProviderResponse)(nil), // 63: openshell.v1.AttachSandboxProviderResponse - (*DetachSandboxProviderResponse)(nil), // 64: openshell.v1.DetachSandboxProviderResponse - (*DeleteSandboxResponse)(nil), // 65: openshell.v1.DeleteSandboxResponse - (*CreateSshSessionRequest)(nil), // 66: openshell.v1.CreateSshSessionRequest - (*CreateSshSessionResponse)(nil), // 67: openshell.v1.CreateSshSessionResponse - (*ExposeServiceRequest)(nil), // 68: openshell.v1.ExposeServiceRequest - (*GetServiceRequest)(nil), // 69: openshell.v1.GetServiceRequest - (*ListServicesRequest)(nil), // 70: openshell.v1.ListServicesRequest - (*ListServicesResponse)(nil), // 71: openshell.v1.ListServicesResponse - (*DeleteServiceRequest)(nil), // 72: openshell.v1.DeleteServiceRequest - (*DeleteServiceResponse)(nil), // 73: openshell.v1.DeleteServiceResponse - (*ServiceEndpoint)(nil), // 74: openshell.v1.ServiceEndpoint - (*ServiceEndpointResponse)(nil), // 75: openshell.v1.ServiceEndpointResponse - (*RevokeSshSessionRequest)(nil), // 76: openshell.v1.RevokeSshSessionRequest - (*RevokeSshSessionResponse)(nil), // 77: openshell.v1.RevokeSshSessionResponse - (*ExecSandboxRequest)(nil), // 78: openshell.v1.ExecSandboxRequest - (*ExecSandboxStdout)(nil), // 79: openshell.v1.ExecSandboxStdout - (*ExecSandboxStderr)(nil), // 80: openshell.v1.ExecSandboxStderr - (*ExecSandboxExit)(nil), // 81: openshell.v1.ExecSandboxExit - (*ExecSandboxEvent)(nil), // 82: openshell.v1.ExecSandboxEvent - (*TcpForwardInit)(nil), // 83: openshell.v1.TcpForwardInit - (*TcpForwardFrame)(nil), // 84: openshell.v1.TcpForwardFrame - (*ExecSandboxInput)(nil), // 85: openshell.v1.ExecSandboxInput - (*ExecSandboxWindowResize)(nil), // 86: openshell.v1.ExecSandboxWindowResize - (*SshSession)(nil), // 87: openshell.v1.SshSession - (*WatchSandboxRequest)(nil), // 88: openshell.v1.WatchSandboxRequest - (*SandboxStreamEvent)(nil), // 89: openshell.v1.SandboxStreamEvent - (*SandboxLogLine)(nil), // 90: openshell.v1.SandboxLogLine - (*SandboxStreamWarning)(nil), // 91: openshell.v1.SandboxStreamWarning - (*CreateProviderRequest)(nil), // 92: openshell.v1.CreateProviderRequest - (*GetProviderRequest)(nil), // 93: openshell.v1.GetProviderRequest - (*ListProvidersRequest)(nil), // 94: openshell.v1.ListProvidersRequest - (*UpdateProviderRequest)(nil), // 95: openshell.v1.UpdateProviderRequest - (*DeleteProviderRequest)(nil), // 96: openshell.v1.DeleteProviderRequest - (*ProviderResponse)(nil), // 97: openshell.v1.ProviderResponse - (*ListProvidersResponse)(nil), // 98: openshell.v1.ListProvidersResponse - (*ListProviderProfilesRequest)(nil), // 99: openshell.v1.ListProviderProfilesRequest - (*GetProviderProfileRequest)(nil), // 100: openshell.v1.GetProviderProfileRequest - (*ProviderProfileImportItem)(nil), // 101: openshell.v1.ProviderProfileImportItem - (*ProviderProfileDiagnostic)(nil), // 102: openshell.v1.ProviderProfileDiagnostic - (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 103: openshell.v1.ProviderCredentialTokenGrantAudienceOverride - (*ProviderCredentialTokenGrantSubjectToken)(nil), // 104: openshell.v1.ProviderCredentialTokenGrantSubjectToken - (*ProviderCredentialTokenGrant)(nil), // 105: openshell.v1.ProviderCredentialTokenGrant - (*ProviderProfileCredential)(nil), // 106: openshell.v1.ProviderProfileCredential - (*ProviderCredentialRefreshMaterial)(nil), // 107: openshell.v1.ProviderCredentialRefreshMaterial - (*ProviderCredentialRefreshOutput)(nil), // 108: openshell.v1.ProviderCredentialRefreshOutput - (*ProviderCredentialRefresh)(nil), // 109: openshell.v1.ProviderCredentialRefresh - (*ProviderCredentialRefreshStatus)(nil), // 110: openshell.v1.ProviderCredentialRefreshStatus - (*ProviderProfileDiscovery)(nil), // 111: openshell.v1.ProviderProfileDiscovery - (*GetProviderRefreshStatusRequest)(nil), // 112: openshell.v1.GetProviderRefreshStatusRequest - (*GetProviderRefreshStatusResponse)(nil), // 113: openshell.v1.GetProviderRefreshStatusResponse - (*ConfigureProviderRefreshRequest)(nil), // 114: openshell.v1.ConfigureProviderRefreshRequest - (*ConfigureProviderRefreshResponse)(nil), // 115: openshell.v1.ConfigureProviderRefreshResponse - (*RotateProviderCredentialRequest)(nil), // 116: openshell.v1.RotateProviderCredentialRequest - (*RotateProviderCredentialResponse)(nil), // 117: openshell.v1.RotateProviderCredentialResponse - (*DeleteProviderRefreshRequest)(nil), // 118: openshell.v1.DeleteProviderRefreshRequest - (*DeleteProviderRefreshResponse)(nil), // 119: openshell.v1.DeleteProviderRefreshResponse - (*ProviderProfile)(nil), // 120: openshell.v1.ProviderProfile - (*ProviderProfileResponse)(nil), // 121: openshell.v1.ProviderProfileResponse - (*ListProviderProfilesResponse)(nil), // 122: openshell.v1.ListProviderProfilesResponse - (*ImportProviderProfilesRequest)(nil), // 123: openshell.v1.ImportProviderProfilesRequest - (*ImportProviderProfilesResponse)(nil), // 124: openshell.v1.ImportProviderProfilesResponse - (*UpdateProviderProfilesRequest)(nil), // 125: openshell.v1.UpdateProviderProfilesRequest - (*UpdateProviderProfilesResponse)(nil), // 126: openshell.v1.UpdateProviderProfilesResponse - (*LintProviderProfilesRequest)(nil), // 127: openshell.v1.LintProviderProfilesRequest - (*LintProviderProfilesResponse)(nil), // 128: openshell.v1.LintProviderProfilesResponse - (*DeleteProviderResponse)(nil), // 129: openshell.v1.DeleteProviderResponse - (*DeleteProviderProfileRequest)(nil), // 130: openshell.v1.DeleteProviderProfileRequest - (*DeleteProviderProfileResponse)(nil), // 131: openshell.v1.DeleteProviderProfileResponse - (*GetSandboxProviderEnvironmentRequest)(nil), // 132: openshell.v1.GetSandboxProviderEnvironmentRequest - (*StaticCredentialEndpointBinding)(nil), // 133: openshell.v1.StaticCredentialEndpointBinding - (*StaticCredentialBinding)(nil), // 134: openshell.v1.StaticCredentialBinding - (*GetSandboxProviderEnvironmentResponse)(nil), // 135: openshell.v1.GetSandboxProviderEnvironmentResponse - (*ProviderEnvironmentValue)(nil), // 136: openshell.v1.ProviderEnvironmentValue - (*ProviderEnvironmentSnapshot)(nil), // 137: openshell.v1.ProviderEnvironmentSnapshot - (*ExchangeProviderSubjectTokenRequest)(nil), // 138: openshell.v1.ExchangeProviderSubjectTokenRequest - (*ExchangeProviderSubjectTokenResponse)(nil), // 139: openshell.v1.ExchangeProviderSubjectTokenResponse - (*UpdateConfigRequest)(nil), // 140: openshell.v1.UpdateConfigRequest - (*PolicyMergeOperation)(nil), // 141: openshell.v1.PolicyMergeOperation - (*AddNetworkRule)(nil), // 142: openshell.v1.AddNetworkRule - (*RemoveNetworkEndpoint)(nil), // 143: openshell.v1.RemoveNetworkEndpoint - (*RemoveNetworkRule)(nil), // 144: openshell.v1.RemoveNetworkRule - (*AddDenyRules)(nil), // 145: openshell.v1.AddDenyRules - (*AddAllowRules)(nil), // 146: openshell.v1.AddAllowRules - (*RemoveNetworkBinary)(nil), // 147: openshell.v1.RemoveNetworkBinary - (*UpdateConfigResponse)(nil), // 148: openshell.v1.UpdateConfigResponse - (*GetSandboxPolicyStatusRequest)(nil), // 149: openshell.v1.GetSandboxPolicyStatusRequest - (*GetSandboxPolicyStatusResponse)(nil), // 150: openshell.v1.GetSandboxPolicyStatusResponse - (*ListSandboxPoliciesRequest)(nil), // 151: openshell.v1.ListSandboxPoliciesRequest - (*ListSandboxPoliciesResponse)(nil), // 152: openshell.v1.ListSandboxPoliciesResponse - (*ReportPolicyStatusRequest)(nil), // 153: openshell.v1.ReportPolicyStatusRequest - (*ReportPolicyStatusResponse)(nil), // 154: openshell.v1.ReportPolicyStatusResponse - (*SandboxPolicyRevision)(nil), // 155: openshell.v1.SandboxPolicyRevision - (*GetSandboxLogsRequest)(nil), // 156: openshell.v1.GetSandboxLogsRequest - (*PushSandboxLogsRequest)(nil), // 157: openshell.v1.PushSandboxLogsRequest - (*PushSandboxLogsResponse)(nil), // 158: openshell.v1.PushSandboxLogsResponse - (*GetSandboxLogsResponse)(nil), // 159: openshell.v1.GetSandboxLogsResponse - (*SupervisorMessage)(nil), // 160: openshell.v1.SupervisorMessage - (*GatewayMessage)(nil), // 161: openshell.v1.GatewayMessage - (*SupervisorHello)(nil), // 162: openshell.v1.SupervisorHello - (*SessionAccepted)(nil), // 163: openshell.v1.SessionAccepted - (*ConfigBootstrap)(nil), // 164: openshell.v1.ConfigBootstrap - (*ConfigUpdate)(nil), // 165: openshell.v1.ConfigUpdate - (*ConfigSnapshotRevision)(nil), // 166: openshell.v1.ConfigSnapshotRevision - (*SandboxConfigRevision)(nil), // 167: openshell.v1.SandboxConfigRevision - (*ConfigApplyFailure)(nil), // 168: openshell.v1.ConfigApplyFailure - (*ConfigComponentApplyResult)(nil), // 169: openshell.v1.ConfigComponentApplyResult - (*ConfigUpdateResult)(nil), // 170: openshell.v1.ConfigUpdateResult - (*ConfigBootstrapResult)(nil), // 171: openshell.v1.ConfigBootstrapResult - (*SessionRejected)(nil), // 172: openshell.v1.SessionRejected - (*SupervisorHeartbeat)(nil), // 173: openshell.v1.SupervisorHeartbeat - (*GatewayHeartbeat)(nil), // 174: openshell.v1.GatewayHeartbeat - (*ReportMainProcessExitRequest)(nil), // 175: openshell.v1.ReportMainProcessExitRequest - (*ReportMainProcessExitResponse)(nil), // 176: openshell.v1.ReportMainProcessExitResponse - (*FinalizeMainProcessExitRequest)(nil), // 177: openshell.v1.FinalizeMainProcessExitRequest - (*FinalizeMainProcessExitResponse)(nil), // 178: openshell.v1.FinalizeMainProcessExitResponse - (*RelayOpen)(nil), // 179: openshell.v1.RelayOpen - (*SshRelayTarget)(nil), // 180: openshell.v1.SshRelayTarget - (*TcpRelayTarget)(nil), // 181: openshell.v1.TcpRelayTarget - (*RelayInit)(nil), // 182: openshell.v1.RelayInit - (*RelayFrame)(nil), // 183: openshell.v1.RelayFrame - (*RelayOpenResult)(nil), // 184: openshell.v1.RelayOpenResult - (*RelayClose)(nil), // 185: openshell.v1.RelayClose - (*L7RequestSample)(nil), // 186: openshell.v1.L7RequestSample - (*DenialSummary)(nil), // 187: openshell.v1.DenialSummary - (*DenialGroupCount)(nil), // 188: openshell.v1.DenialGroupCount - (*NetworkActivitySummary)(nil), // 189: openshell.v1.NetworkActivitySummary - (*PolicyChunk)(nil), // 190: openshell.v1.PolicyChunk - (*DraftPolicyUpdate)(nil), // 191: openshell.v1.DraftPolicyUpdate - (*SubmitPolicyAnalysisRequest)(nil), // 192: openshell.v1.SubmitPolicyAnalysisRequest - (*SubmitPolicyAnalysisResponse)(nil), // 193: openshell.v1.SubmitPolicyAnalysisResponse - (*GetDraftPolicyRequest)(nil), // 194: openshell.v1.GetDraftPolicyRequest - (*GetDraftPolicyResponse)(nil), // 195: openshell.v1.GetDraftPolicyResponse - (*ApproveDraftChunkRequest)(nil), // 196: openshell.v1.ApproveDraftChunkRequest - (*ApproveDraftChunkResponse)(nil), // 197: openshell.v1.ApproveDraftChunkResponse - (*RejectDraftChunkRequest)(nil), // 198: openshell.v1.RejectDraftChunkRequest - (*RejectDraftChunkResponse)(nil), // 199: openshell.v1.RejectDraftChunkResponse - (*DraftChunkApproval)(nil), // 200: openshell.v1.DraftChunkApproval - (*ApproveAllDraftChunksRequest)(nil), // 201: openshell.v1.ApproveAllDraftChunksRequest - (*ApproveAllDraftChunksResponse)(nil), // 202: openshell.v1.ApproveAllDraftChunksResponse - (*EditDraftChunkRequest)(nil), // 203: openshell.v1.EditDraftChunkRequest - (*EditDraftChunkResponse)(nil), // 204: openshell.v1.EditDraftChunkResponse - (*UndoDraftChunkRequest)(nil), // 205: openshell.v1.UndoDraftChunkRequest - (*UndoDraftChunkResponse)(nil), // 206: openshell.v1.UndoDraftChunkResponse - (*ClearDraftChunksRequest)(nil), // 207: openshell.v1.ClearDraftChunksRequest - (*ClearDraftChunksResponse)(nil), // 208: openshell.v1.ClearDraftChunksResponse - (*GetDraftHistoryRequest)(nil), // 209: openshell.v1.GetDraftHistoryRequest - (*DraftHistoryEntry)(nil), // 210: openshell.v1.DraftHistoryEntry - (*GetDraftHistoryResponse)(nil), // 211: openshell.v1.GetDraftHistoryResponse - (*CreateWorkspaceRequest)(nil), // 212: openshell.v1.CreateWorkspaceRequest - (*CreateWorkspaceResponse)(nil), // 213: openshell.v1.CreateWorkspaceResponse - (*GetWorkspaceRequest)(nil), // 214: openshell.v1.GetWorkspaceRequest - (*GetWorkspaceResponse)(nil), // 215: openshell.v1.GetWorkspaceResponse - (*ListWorkspacesRequest)(nil), // 216: openshell.v1.ListWorkspacesRequest - (*ListWorkspacesResponse)(nil), // 217: openshell.v1.ListWorkspacesResponse - (*DeleteWorkspaceRequest)(nil), // 218: openshell.v1.DeleteWorkspaceRequest - (*DeleteWorkspaceResponse)(nil), // 219: openshell.v1.DeleteWorkspaceResponse - (*WorkspaceMember)(nil), // 220: openshell.v1.WorkspaceMember - (*AddWorkspaceMemberRequest)(nil), // 221: openshell.v1.AddWorkspaceMemberRequest - (*AddWorkspaceMemberResponse)(nil), // 222: openshell.v1.AddWorkspaceMemberResponse - (*RemoveWorkspaceMemberRequest)(nil), // 223: openshell.v1.RemoveWorkspaceMemberRequest - (*RemoveWorkspaceMemberResponse)(nil), // 224: openshell.v1.RemoveWorkspaceMemberResponse - (*ListWorkspaceMembersRequest)(nil), // 225: openshell.v1.ListWorkspaceMembersRequest - (*ListWorkspaceMembersResponse)(nil), // 226: openshell.v1.ListWorkspaceMembersResponse - (*ExtensionServiceCredential)(nil), // 227: openshell.v1.ExtensionServiceCredential - nil, // 228: openshell.v1.SandboxSpec.EnvironmentEntry - nil, // 229: openshell.v1.SandboxTemplate.LabelsEntry - nil, // 230: openshell.v1.SandboxTemplate.AnnotationsEntry - nil, // 231: openshell.v1.SandboxTemplate.EnvironmentEntry - nil, // 232: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry - nil, // 233: openshell.v1.PlatformEvent.MetadataEntry - nil, // 234: openshell.v1.CreateSandboxRequest.LabelsEntry - nil, // 235: openshell.v1.CreateSandboxRequest.AnnotationsEntry - nil, // 236: openshell.v1.ExecSandboxRequest.EnvironmentEntry - nil, // 237: openshell.v1.SandboxLogLine.FieldsEntry - nil, // 238: openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry - nil, // 239: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - nil, // 240: openshell.v1.ProviderProfile.AnnotationsEntry - nil, // 241: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - nil, // 242: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry - nil, // 243: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - nil, // 244: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - nil, // 245: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry - nil, // 246: openshell.v1.UpdateConfigRequest.AnnotationsEntry - nil, // 247: openshell.v1.UpdateConfigResponse.AnnotationsEntry - nil, // 248: openshell.v1.SandboxPolicyRevision.ProvenanceEntry - nil, // 249: openshell.v1.CreateWorkspaceRequest.LabelsEntry - (*datamodelv1.ObjectMeta)(nil), // 250: openshell.datamodel.v1.ObjectMeta - (*sandboxv1.SandboxPolicy)(nil), // 251: openshell.sandbox.v1.SandboxPolicy - (*structpb.Struct)(nil), // 252: google.protobuf.Struct - (*durationpb.Duration)(nil), // 253: google.protobuf.Duration - (*datamodelv1.WorkspaceSelector)(nil), // 254: openshell.datamodel.v1.WorkspaceSelector - (*datamodelv1.Provider)(nil), // 255: openshell.datamodel.v1.Provider - (*sandboxv1.NetworkEndpoint)(nil), // 256: openshell.sandbox.v1.NetworkEndpoint - (*sandboxv1.NetworkBinary)(nil), // 257: openshell.sandbox.v1.NetworkBinary - (*sandboxv1.SettingValue)(nil), // 258: openshell.sandbox.v1.SettingValue - (*sandboxv1.NetworkPolicyRule)(nil), // 259: openshell.sandbox.v1.NetworkPolicyRule - (*sandboxv1.L7DenyRule)(nil), // 260: openshell.sandbox.v1.L7DenyRule - (*sandboxv1.L7Rule)(nil), // 261: openshell.sandbox.v1.L7Rule - (*sandboxv1.SandboxConfigSnapshot)(nil), // 262: openshell.sandbox.v1.SandboxConfigSnapshot - (sandboxv1.PolicySource)(0), // 263: openshell.sandbox.v1.PolicySource - (*datamodelv1.Workspace)(nil), // 264: openshell.datamodel.v1.Workspace - (*sandboxv1.GetSandboxConfigRequest)(nil), // 265: openshell.sandbox.v1.GetSandboxConfigRequest - (*sandboxv1.GetGatewayConfigRequest)(nil), // 266: openshell.sandbox.v1.GetGatewayConfigRequest - (*sandboxv1.GetSandboxConfigResponse)(nil), // 267: openshell.sandbox.v1.GetSandboxConfigResponse - (*sandboxv1.GetGatewayConfigResponse)(nil), // 268: openshell.sandbox.v1.GetGatewayConfigResponse + (ConfigUpdateConsistency)(0), // 5: openshell.v1.ConfigUpdateConsistency + (ConfigUpdateOperationState)(0), // 6: openshell.v1.ConfigUpdateOperationState + (PolicyStatus)(0), // 7: openshell.v1.PolicyStatus + (ConfigComponent)(0), // 8: openshell.v1.ConfigComponent + (ConfigApplyOutcome)(0), // 9: openshell.v1.ConfigApplyOutcome + (ServiceStatus)(0), // 10: openshell.v1.ServiceStatus + (WorkspaceRole)(0), // 11: openshell.v1.WorkspaceRole + (ProviderCredentialRefreshRecoveryAction)(0), // 12: openshell.v1.ProviderCredentialRefreshRecoveryAction + (*IssueSandboxTokenRequest)(nil), // 13: openshell.v1.IssueSandboxTokenRequest + (*IssueSandboxTokenResponse)(nil), // 14: openshell.v1.IssueSandboxTokenResponse + (*RefreshSandboxTokenRequest)(nil), // 15: openshell.v1.RefreshSandboxTokenRequest + (*RefreshSandboxTokenResponse)(nil), // 16: openshell.v1.RefreshSandboxTokenResponse + (*HealthRequest)(nil), // 17: openshell.v1.HealthRequest + (*HealthResponse)(nil), // 18: openshell.v1.HealthResponse + (*GetCurrentUserRequest)(nil), // 19: openshell.v1.GetCurrentUserRequest + (*GetCurrentUserResponse)(nil), // 20: openshell.v1.GetCurrentUserResponse + (*GetGatewayInfoRequest)(nil), // 21: openshell.v1.GetGatewayInfoRequest + (*GetGatewayInfoResponse)(nil), // 22: openshell.v1.GetGatewayInfoResponse + (*ComputeDriverInfo)(nil), // 23: openshell.v1.ComputeDriverInfo + (*ComputeDriverCapabilities)(nil), // 24: openshell.v1.ComputeDriverCapabilities + (*ResourceCapabilities)(nil), // 25: openshell.v1.ResourceCapabilities + (*CpuResourceCapabilities)(nil), // 26: openshell.v1.CpuResourceCapabilities + (*MemoryResourceCapabilities)(nil), // 27: openshell.v1.MemoryResourceCapabilities + (*GpuResourceCapabilities)(nil), // 28: openshell.v1.GpuResourceCapabilities + (*Sandbox)(nil), // 29: openshell.v1.Sandbox + (*SandboxSpec)(nil), // 30: openshell.v1.SandboxSpec + (*ResourceRequirements)(nil), // 31: openshell.v1.ResourceRequirements + (*GpuResourceRequirements)(nil), // 32: openshell.v1.GpuResourceRequirements + (*SandboxTemplate)(nil), // 33: openshell.v1.SandboxTemplate + (*SandboxWorkloadTemplate)(nil), // 34: openshell.v1.SandboxWorkloadTemplate + (*SandboxWorkloadTemplateSpec)(nil), // 35: openshell.v1.SandboxWorkloadTemplateSpec + (*SandboxWorkloadConfig)(nil), // 36: openshell.v1.SandboxWorkloadConfig + (*SandboxResources)(nil), // 37: openshell.v1.SandboxResources + (*SandboxServiceLevel)(nil), // 38: openshell.v1.SandboxServiceLevel + (*SandboxStartup)(nil), // 39: openshell.v1.SandboxStartup + (*SandboxWorkloadTemplateProvenance)(nil), // 40: openshell.v1.SandboxWorkloadTemplateProvenance + (*SandboxStatus)(nil), // 41: openshell.v1.SandboxStatus + (*SandboxCondition)(nil), // 42: openshell.v1.SandboxCondition + (*PlatformEvent)(nil), // 43: openshell.v1.PlatformEvent + (*CreateSandboxRequest)(nil), // 44: openshell.v1.CreateSandboxRequest + (*CreateSandboxTemplateRequest)(nil), // 45: openshell.v1.CreateSandboxTemplateRequest + (*GetSandboxTemplateRequest)(nil), // 46: openshell.v1.GetSandboxTemplateRequest + (*ListSandboxTemplatesRequest)(nil), // 47: openshell.v1.ListSandboxTemplatesRequest + (*DeleteSandboxTemplateRequest)(nil), // 48: openshell.v1.DeleteSandboxTemplateRequest + (*SandboxTemplateResponse)(nil), // 49: openshell.v1.SandboxTemplateResponse + (*ListSandboxTemplatesResponse)(nil), // 50: openshell.v1.ListSandboxTemplatesResponse + (*DeleteSandboxTemplateResponse)(nil), // 51: openshell.v1.DeleteSandboxTemplateResponse + (*BeginRootfsTarStagingRequest)(nil), // 52: openshell.v1.BeginRootfsTarStagingRequest + (*BeginRootfsTarStagingResponse)(nil), // 53: openshell.v1.BeginRootfsTarStagingResponse + (*GetSandboxRequest)(nil), // 54: openshell.v1.GetSandboxRequest + (*ListSandboxesRequest)(nil), // 55: openshell.v1.ListSandboxesRequest + (*ListSandboxProvidersRequest)(nil), // 56: openshell.v1.ListSandboxProvidersRequest + (*AttachSandboxProviderRequest)(nil), // 57: openshell.v1.AttachSandboxProviderRequest + (*DetachSandboxProviderRequest)(nil), // 58: openshell.v1.DetachSandboxProviderRequest + (*DeleteSandboxRequest)(nil), // 59: openshell.v1.DeleteSandboxRequest + (*StopSandboxRequest)(nil), // 60: openshell.v1.StopSandboxRequest + (*StartSandboxRequest)(nil), // 61: openshell.v1.StartSandboxRequest + (*SandboxResponse)(nil), // 62: openshell.v1.SandboxResponse + (*ListSandboxesResponse)(nil), // 63: openshell.v1.ListSandboxesResponse + (*ListSandboxProvidersResponse)(nil), // 64: openshell.v1.ListSandboxProvidersResponse + (*AttachSandboxProviderResponse)(nil), // 65: openshell.v1.AttachSandboxProviderResponse + (*DetachSandboxProviderResponse)(nil), // 66: openshell.v1.DetachSandboxProviderResponse + (*DeleteSandboxResponse)(nil), // 67: openshell.v1.DeleteSandboxResponse + (*CreateSshSessionRequest)(nil), // 68: openshell.v1.CreateSshSessionRequest + (*CreateSshSessionResponse)(nil), // 69: openshell.v1.CreateSshSessionResponse + (*ExposeServiceRequest)(nil), // 70: openshell.v1.ExposeServiceRequest + (*GetServiceRequest)(nil), // 71: openshell.v1.GetServiceRequest + (*ListServicesRequest)(nil), // 72: openshell.v1.ListServicesRequest + (*ListServicesResponse)(nil), // 73: openshell.v1.ListServicesResponse + (*DeleteServiceRequest)(nil), // 74: openshell.v1.DeleteServiceRequest + (*DeleteServiceResponse)(nil), // 75: openshell.v1.DeleteServiceResponse + (*ServiceEndpoint)(nil), // 76: openshell.v1.ServiceEndpoint + (*ServiceEndpointResponse)(nil), // 77: openshell.v1.ServiceEndpointResponse + (*RevokeSshSessionRequest)(nil), // 78: openshell.v1.RevokeSshSessionRequest + (*RevokeSshSessionResponse)(nil), // 79: openshell.v1.RevokeSshSessionResponse + (*ExecSandboxRequest)(nil), // 80: openshell.v1.ExecSandboxRequest + (*ExecSandboxStdout)(nil), // 81: openshell.v1.ExecSandboxStdout + (*ExecSandboxStderr)(nil), // 82: openshell.v1.ExecSandboxStderr + (*ExecSandboxExit)(nil), // 83: openshell.v1.ExecSandboxExit + (*ExecSandboxEvent)(nil), // 84: openshell.v1.ExecSandboxEvent + (*TcpForwardInit)(nil), // 85: openshell.v1.TcpForwardInit + (*TcpForwardFrame)(nil), // 86: openshell.v1.TcpForwardFrame + (*ExecSandboxInput)(nil), // 87: openshell.v1.ExecSandboxInput + (*ExecSandboxWindowResize)(nil), // 88: openshell.v1.ExecSandboxWindowResize + (*SshSession)(nil), // 89: openshell.v1.SshSession + (*WatchSandboxRequest)(nil), // 90: openshell.v1.WatchSandboxRequest + (*SandboxStreamEvent)(nil), // 91: openshell.v1.SandboxStreamEvent + (*SandboxLogLine)(nil), // 92: openshell.v1.SandboxLogLine + (*SandboxStreamWarning)(nil), // 93: openshell.v1.SandboxStreamWarning + (*CreateProviderRequest)(nil), // 94: openshell.v1.CreateProviderRequest + (*GetProviderRequest)(nil), // 95: openshell.v1.GetProviderRequest + (*ListProvidersRequest)(nil), // 96: openshell.v1.ListProvidersRequest + (*UpdateProviderRequest)(nil), // 97: openshell.v1.UpdateProviderRequest + (*DeleteProviderRequest)(nil), // 98: openshell.v1.DeleteProviderRequest + (*ProviderResponse)(nil), // 99: openshell.v1.ProviderResponse + (*ListProvidersResponse)(nil), // 100: openshell.v1.ListProvidersResponse + (*ListProviderProfilesRequest)(nil), // 101: openshell.v1.ListProviderProfilesRequest + (*GetProviderProfileRequest)(nil), // 102: openshell.v1.GetProviderProfileRequest + (*ProviderProfileImportItem)(nil), // 103: openshell.v1.ProviderProfileImportItem + (*ProviderProfileDiagnostic)(nil), // 104: openshell.v1.ProviderProfileDiagnostic + (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 105: openshell.v1.ProviderCredentialTokenGrantAudienceOverride + (*ProviderCredentialTokenGrantSubjectToken)(nil), // 106: openshell.v1.ProviderCredentialTokenGrantSubjectToken + (*ProviderCredentialTokenGrant)(nil), // 107: openshell.v1.ProviderCredentialTokenGrant + (*ProviderProfileCredential)(nil), // 108: openshell.v1.ProviderProfileCredential + (*ProviderCredentialRefreshMaterial)(nil), // 109: openshell.v1.ProviderCredentialRefreshMaterial + (*ProviderCredentialRefreshOutput)(nil), // 110: openshell.v1.ProviderCredentialRefreshOutput + (*ProviderCredentialRefresh)(nil), // 111: openshell.v1.ProviderCredentialRefresh + (*ProviderCredentialRefreshStatus)(nil), // 112: openshell.v1.ProviderCredentialRefreshStatus + (*ProviderProfileDiscovery)(nil), // 113: openshell.v1.ProviderProfileDiscovery + (*GetProviderRefreshStatusRequest)(nil), // 114: openshell.v1.GetProviderRefreshStatusRequest + (*GetProviderRefreshStatusResponse)(nil), // 115: openshell.v1.GetProviderRefreshStatusResponse + (*ConfigureProviderRefreshRequest)(nil), // 116: openshell.v1.ConfigureProviderRefreshRequest + (*ConfigureProviderRefreshResponse)(nil), // 117: openshell.v1.ConfigureProviderRefreshResponse + (*RotateProviderCredentialRequest)(nil), // 118: openshell.v1.RotateProviderCredentialRequest + (*RotateProviderCredentialResponse)(nil), // 119: openshell.v1.RotateProviderCredentialResponse + (*DeleteProviderRefreshRequest)(nil), // 120: openshell.v1.DeleteProviderRefreshRequest + (*DeleteProviderRefreshResponse)(nil), // 121: openshell.v1.DeleteProviderRefreshResponse + (*ProviderProfile)(nil), // 122: openshell.v1.ProviderProfile + (*ProviderProfileResponse)(nil), // 123: openshell.v1.ProviderProfileResponse + (*ListProviderProfilesResponse)(nil), // 124: openshell.v1.ListProviderProfilesResponse + (*ImportProviderProfilesRequest)(nil), // 125: openshell.v1.ImportProviderProfilesRequest + (*ImportProviderProfilesResponse)(nil), // 126: openshell.v1.ImportProviderProfilesResponse + (*UpdateProviderProfilesRequest)(nil), // 127: openshell.v1.UpdateProviderProfilesRequest + (*UpdateProviderProfilesResponse)(nil), // 128: openshell.v1.UpdateProviderProfilesResponse + (*LintProviderProfilesRequest)(nil), // 129: openshell.v1.LintProviderProfilesRequest + (*LintProviderProfilesResponse)(nil), // 130: openshell.v1.LintProviderProfilesResponse + (*DeleteProviderResponse)(nil), // 131: openshell.v1.DeleteProviderResponse + (*DeleteProviderProfileRequest)(nil), // 132: openshell.v1.DeleteProviderProfileRequest + (*DeleteProviderProfileResponse)(nil), // 133: openshell.v1.DeleteProviderProfileResponse + (*GetSandboxProviderEnvironmentRequest)(nil), // 134: openshell.v1.GetSandboxProviderEnvironmentRequest + (*StaticCredentialEndpointBinding)(nil), // 135: openshell.v1.StaticCredentialEndpointBinding + (*StaticCredentialBinding)(nil), // 136: openshell.v1.StaticCredentialBinding + (*GetSandboxProviderEnvironmentResponse)(nil), // 137: openshell.v1.GetSandboxProviderEnvironmentResponse + (*ProviderEnvironmentValue)(nil), // 138: openshell.v1.ProviderEnvironmentValue + (*ProviderEnvironmentSnapshot)(nil), // 139: openshell.v1.ProviderEnvironmentSnapshot + (*ExchangeProviderSubjectTokenRequest)(nil), // 140: openshell.v1.ExchangeProviderSubjectTokenRequest + (*ExchangeProviderSubjectTokenResponse)(nil), // 141: openshell.v1.ExchangeProviderSubjectTokenResponse + (*UpdateConfigRequest)(nil), // 142: openshell.v1.UpdateConfigRequest + (*ConfigUpdateOperation)(nil), // 143: openshell.v1.ConfigUpdateOperation + (*GetConfigUpdateOperationRequest)(nil), // 144: openshell.v1.GetConfigUpdateOperationRequest + (*GetConfigUpdateOperationResponse)(nil), // 145: openshell.v1.GetConfigUpdateOperationResponse + (*PolicyMergeOperation)(nil), // 146: openshell.v1.PolicyMergeOperation + (*AddNetworkRule)(nil), // 147: openshell.v1.AddNetworkRule + (*RemoveNetworkEndpoint)(nil), // 148: openshell.v1.RemoveNetworkEndpoint + (*RemoveNetworkRule)(nil), // 149: openshell.v1.RemoveNetworkRule + (*AddDenyRules)(nil), // 150: openshell.v1.AddDenyRules + (*AddAllowRules)(nil), // 151: openshell.v1.AddAllowRules + (*RemoveNetworkBinary)(nil), // 152: openshell.v1.RemoveNetworkBinary + (*UpdateConfigResponse)(nil), // 153: openshell.v1.UpdateConfigResponse + (*GetSandboxPolicyStatusRequest)(nil), // 154: openshell.v1.GetSandboxPolicyStatusRequest + (*GetSandboxPolicyStatusResponse)(nil), // 155: openshell.v1.GetSandboxPolicyStatusResponse + (*ListSandboxPoliciesRequest)(nil), // 156: openshell.v1.ListSandboxPoliciesRequest + (*ListSandboxPoliciesResponse)(nil), // 157: openshell.v1.ListSandboxPoliciesResponse + (*ReportPolicyStatusRequest)(nil), // 158: openshell.v1.ReportPolicyStatusRequest + (*ReportPolicyStatusResponse)(nil), // 159: openshell.v1.ReportPolicyStatusResponse + (*SandboxPolicyRevision)(nil), // 160: openshell.v1.SandboxPolicyRevision + (*GetSandboxLogsRequest)(nil), // 161: openshell.v1.GetSandboxLogsRequest + (*PushSandboxLogsRequest)(nil), // 162: openshell.v1.PushSandboxLogsRequest + (*PushSandboxLogsResponse)(nil), // 163: openshell.v1.PushSandboxLogsResponse + (*GetSandboxLogsResponse)(nil), // 164: openshell.v1.GetSandboxLogsResponse + (*SupervisorMessage)(nil), // 165: openshell.v1.SupervisorMessage + (*GatewayMessage)(nil), // 166: openshell.v1.GatewayMessage + (*SupervisorHello)(nil), // 167: openshell.v1.SupervisorHello + (*SessionAccepted)(nil), // 168: openshell.v1.SessionAccepted + (*ConfigBootstrap)(nil), // 169: openshell.v1.ConfigBootstrap + (*ConfigUpdate)(nil), // 170: openshell.v1.ConfigUpdate + (*ConfigSnapshotRevision)(nil), // 171: openshell.v1.ConfigSnapshotRevision + (*SandboxConfigRevision)(nil), // 172: openshell.v1.SandboxConfigRevision + (*ConfigApplyFailure)(nil), // 173: openshell.v1.ConfigApplyFailure + (*ConfigComponentApplyResult)(nil), // 174: openshell.v1.ConfigComponentApplyResult + (*ConfigUpdateResult)(nil), // 175: openshell.v1.ConfigUpdateResult + (*ConfigBootstrapResult)(nil), // 176: openshell.v1.ConfigBootstrapResult + (*SessionRejected)(nil), // 177: openshell.v1.SessionRejected + (*SupervisorHeartbeat)(nil), // 178: openshell.v1.SupervisorHeartbeat + (*GatewayHeartbeat)(nil), // 179: openshell.v1.GatewayHeartbeat + (*ReportMainProcessExitRequest)(nil), // 180: openshell.v1.ReportMainProcessExitRequest + (*ReportMainProcessExitResponse)(nil), // 181: openshell.v1.ReportMainProcessExitResponse + (*FinalizeMainProcessExitRequest)(nil), // 182: openshell.v1.FinalizeMainProcessExitRequest + (*FinalizeMainProcessExitResponse)(nil), // 183: openshell.v1.FinalizeMainProcessExitResponse + (*RelayOpen)(nil), // 184: openshell.v1.RelayOpen + (*SshRelayTarget)(nil), // 185: openshell.v1.SshRelayTarget + (*TcpRelayTarget)(nil), // 186: openshell.v1.TcpRelayTarget + (*RelayInit)(nil), // 187: openshell.v1.RelayInit + (*RelayFrame)(nil), // 188: openshell.v1.RelayFrame + (*RelayOpenResult)(nil), // 189: openshell.v1.RelayOpenResult + (*RelayClose)(nil), // 190: openshell.v1.RelayClose + (*L7RequestSample)(nil), // 191: openshell.v1.L7RequestSample + (*DenialSummary)(nil), // 192: openshell.v1.DenialSummary + (*DenialGroupCount)(nil), // 193: openshell.v1.DenialGroupCount + (*NetworkActivitySummary)(nil), // 194: openshell.v1.NetworkActivitySummary + (*PolicyChunk)(nil), // 195: openshell.v1.PolicyChunk + (*DraftPolicyUpdate)(nil), // 196: openshell.v1.DraftPolicyUpdate + (*SubmitPolicyAnalysisRequest)(nil), // 197: openshell.v1.SubmitPolicyAnalysisRequest + (*SubmitPolicyAnalysisResponse)(nil), // 198: openshell.v1.SubmitPolicyAnalysisResponse + (*GetDraftPolicyRequest)(nil), // 199: openshell.v1.GetDraftPolicyRequest + (*GetDraftPolicyResponse)(nil), // 200: openshell.v1.GetDraftPolicyResponse + (*ApproveDraftChunkRequest)(nil), // 201: openshell.v1.ApproveDraftChunkRequest + (*ApproveDraftChunkResponse)(nil), // 202: openshell.v1.ApproveDraftChunkResponse + (*RejectDraftChunkRequest)(nil), // 203: openshell.v1.RejectDraftChunkRequest + (*RejectDraftChunkResponse)(nil), // 204: openshell.v1.RejectDraftChunkResponse + (*DraftChunkApproval)(nil), // 205: openshell.v1.DraftChunkApproval + (*ApproveAllDraftChunksRequest)(nil), // 206: openshell.v1.ApproveAllDraftChunksRequest + (*ApproveAllDraftChunksResponse)(nil), // 207: openshell.v1.ApproveAllDraftChunksResponse + (*EditDraftChunkRequest)(nil), // 208: openshell.v1.EditDraftChunkRequest + (*EditDraftChunkResponse)(nil), // 209: openshell.v1.EditDraftChunkResponse + (*UndoDraftChunkRequest)(nil), // 210: openshell.v1.UndoDraftChunkRequest + (*UndoDraftChunkResponse)(nil), // 211: openshell.v1.UndoDraftChunkResponse + (*ClearDraftChunksRequest)(nil), // 212: openshell.v1.ClearDraftChunksRequest + (*ClearDraftChunksResponse)(nil), // 213: openshell.v1.ClearDraftChunksResponse + (*GetDraftHistoryRequest)(nil), // 214: openshell.v1.GetDraftHistoryRequest + (*DraftHistoryEntry)(nil), // 215: openshell.v1.DraftHistoryEntry + (*GetDraftHistoryResponse)(nil), // 216: openshell.v1.GetDraftHistoryResponse + (*CreateWorkspaceRequest)(nil), // 217: openshell.v1.CreateWorkspaceRequest + (*CreateWorkspaceResponse)(nil), // 218: openshell.v1.CreateWorkspaceResponse + (*GetWorkspaceRequest)(nil), // 219: openshell.v1.GetWorkspaceRequest + (*GetWorkspaceResponse)(nil), // 220: openshell.v1.GetWorkspaceResponse + (*ListWorkspacesRequest)(nil), // 221: openshell.v1.ListWorkspacesRequest + (*ListWorkspacesResponse)(nil), // 222: openshell.v1.ListWorkspacesResponse + (*DeleteWorkspaceRequest)(nil), // 223: openshell.v1.DeleteWorkspaceRequest + (*DeleteWorkspaceResponse)(nil), // 224: openshell.v1.DeleteWorkspaceResponse + (*WorkspaceMember)(nil), // 225: openshell.v1.WorkspaceMember + (*AddWorkspaceMemberRequest)(nil), // 226: openshell.v1.AddWorkspaceMemberRequest + (*AddWorkspaceMemberResponse)(nil), // 227: openshell.v1.AddWorkspaceMemberResponse + (*RemoveWorkspaceMemberRequest)(nil), // 228: openshell.v1.RemoveWorkspaceMemberRequest + (*RemoveWorkspaceMemberResponse)(nil), // 229: openshell.v1.RemoveWorkspaceMemberResponse + (*ListWorkspaceMembersRequest)(nil), // 230: openshell.v1.ListWorkspaceMembersRequest + (*ListWorkspaceMembersResponse)(nil), // 231: openshell.v1.ListWorkspaceMembersResponse + (*ExtensionServiceCredential)(nil), // 232: openshell.v1.ExtensionServiceCredential + nil, // 233: openshell.v1.SandboxSpec.EnvironmentEntry + nil, // 234: openshell.v1.SandboxTemplate.LabelsEntry + nil, // 235: openshell.v1.SandboxTemplate.AnnotationsEntry + nil, // 236: openshell.v1.SandboxTemplate.EnvironmentEntry + nil, // 237: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + nil, // 238: openshell.v1.PlatformEvent.MetadataEntry + nil, // 239: openshell.v1.CreateSandboxRequest.LabelsEntry + nil, // 240: openshell.v1.CreateSandboxRequest.AnnotationsEntry + nil, // 241: openshell.v1.ExecSandboxRequest.EnvironmentEntry + nil, // 242: openshell.v1.SandboxLogLine.FieldsEntry + nil, // 243: openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry + nil, // 244: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + nil, // 245: openshell.v1.ProviderProfile.AnnotationsEntry + nil, // 246: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + nil, // 247: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry + nil, // 248: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + nil, // 249: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + nil, // 250: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry + nil, // 251: openshell.v1.UpdateConfigRequest.AnnotationsEntry + nil, // 252: openshell.v1.UpdateConfigResponse.AnnotationsEntry + nil, // 253: openshell.v1.SandboxPolicyRevision.ProvenanceEntry + nil, // 254: openshell.v1.CreateWorkspaceRequest.LabelsEntry + (*datamodelv1.ObjectMeta)(nil), // 255: openshell.datamodel.v1.ObjectMeta + (*sandboxv1.SandboxPolicy)(nil), // 256: openshell.sandbox.v1.SandboxPolicy + (*structpb.Struct)(nil), // 257: google.protobuf.Struct + (*durationpb.Duration)(nil), // 258: google.protobuf.Duration + (*datamodelv1.WorkspaceSelector)(nil), // 259: openshell.datamodel.v1.WorkspaceSelector + (*datamodelv1.Provider)(nil), // 260: openshell.datamodel.v1.Provider + (*sandboxv1.NetworkEndpoint)(nil), // 261: openshell.sandbox.v1.NetworkEndpoint + (*sandboxv1.NetworkBinary)(nil), // 262: openshell.sandbox.v1.NetworkBinary + (*sandboxv1.SettingValue)(nil), // 263: openshell.sandbox.v1.SettingValue + (*sandboxv1.NetworkPolicyRule)(nil), // 264: openshell.sandbox.v1.NetworkPolicyRule + (*sandboxv1.L7DenyRule)(nil), // 265: openshell.sandbox.v1.L7DenyRule + (*sandboxv1.L7Rule)(nil), // 266: openshell.sandbox.v1.L7Rule + (*sandboxv1.SandboxConfigSnapshot)(nil), // 267: openshell.sandbox.v1.SandboxConfigSnapshot + (sandboxv1.PolicySource)(0), // 268: openshell.sandbox.v1.PolicySource + (*datamodelv1.Workspace)(nil), // 269: openshell.datamodel.v1.Workspace + (*sandboxv1.GetSandboxConfigRequest)(nil), // 270: openshell.sandbox.v1.GetSandboxConfigRequest + (*sandboxv1.GetGatewayConfigRequest)(nil), // 271: openshell.sandbox.v1.GetGatewayConfigRequest + (*sandboxv1.GetSandboxConfigResponse)(nil), // 272: openshell.sandbox.v1.GetSandboxConfigResponse + (*sandboxv1.GetGatewayConfigResponse)(nil), // 273: openshell.sandbox.v1.GetGatewayConfigResponse } var file_openshell_proto_depIdxs = []int32{ - 227, // 0: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential - 8, // 1: openshell.v1.HealthResponse.status:type_name -> openshell.v1.ServiceStatus - 8, // 2: openshell.v1.GetGatewayInfoResponse.status:type_name -> openshell.v1.ServiceStatus - 21, // 3: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo - 22, // 4: openshell.v1.ComputeDriverInfo.capabilities:type_name -> openshell.v1.ComputeDriverCapabilities - 23, // 5: openshell.v1.ComputeDriverCapabilities.resource_capabilities:type_name -> openshell.v1.ResourceCapabilities - 24, // 6: openshell.v1.ResourceCapabilities.cpu:type_name -> openshell.v1.CpuResourceCapabilities - 25, // 7: openshell.v1.ResourceCapabilities.memory:type_name -> openshell.v1.MemoryResourceCapabilities - 26, // 8: openshell.v1.ResourceCapabilities.gpu:type_name -> openshell.v1.GpuResourceCapabilities - 250, // 9: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 28, // 10: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec - 39, // 11: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus - 38, // 12: openshell.v1.Sandbox.created_from_workload_template:type_name -> openshell.v1.SandboxWorkloadTemplateProvenance - 228, // 13: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry - 31, // 14: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate - 251, // 15: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 29, // 16: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements - 30, // 17: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements - 229, // 18: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry - 230, // 19: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry - 231, // 20: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry - 252, // 21: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct - 252, // 22: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct - 250, // 23: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 33, // 24: openshell.v1.SandboxWorkloadTemplate.spec:type_name -> openshell.v1.SandboxWorkloadTemplateSpec - 34, // 25: openshell.v1.SandboxWorkloadTemplateSpec.workload:type_name -> openshell.v1.SandboxWorkloadConfig - 252, // 26: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct - 36, // 27: openshell.v1.SandboxWorkloadTemplateSpec.desired_service_level:type_name -> openshell.v1.SandboxServiceLevel - 232, // 28: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry - 35, // 29: openshell.v1.SandboxWorkloadConfig.resources:type_name -> openshell.v1.SandboxResources - 30, // 30: openshell.v1.SandboxResources.gpu:type_name -> openshell.v1.GpuResourceRequirements - 37, // 31: openshell.v1.SandboxServiceLevel.startup:type_name -> openshell.v1.SandboxStartup - 253, // 32: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration - 40, // 33: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition + 232, // 0: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential + 10, // 1: openshell.v1.HealthResponse.status:type_name -> openshell.v1.ServiceStatus + 10, // 2: openshell.v1.GetGatewayInfoResponse.status:type_name -> openshell.v1.ServiceStatus + 23, // 3: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo + 24, // 4: openshell.v1.ComputeDriverInfo.capabilities:type_name -> openshell.v1.ComputeDriverCapabilities + 25, // 5: openshell.v1.ComputeDriverCapabilities.resource_capabilities:type_name -> openshell.v1.ResourceCapabilities + 26, // 6: openshell.v1.ResourceCapabilities.cpu:type_name -> openshell.v1.CpuResourceCapabilities + 27, // 7: openshell.v1.ResourceCapabilities.memory:type_name -> openshell.v1.MemoryResourceCapabilities + 28, // 8: openshell.v1.ResourceCapabilities.gpu:type_name -> openshell.v1.GpuResourceCapabilities + 255, // 9: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 30, // 10: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec + 41, // 11: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus + 40, // 12: openshell.v1.Sandbox.created_from_workload_template:type_name -> openshell.v1.SandboxWorkloadTemplateProvenance + 233, // 13: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry + 33, // 14: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate + 256, // 15: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 31, // 16: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements + 32, // 17: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements + 234, // 18: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry + 235, // 19: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry + 236, // 20: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry + 257, // 21: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct + 257, // 22: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct + 255, // 23: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 35, // 24: openshell.v1.SandboxWorkloadTemplate.spec:type_name -> openshell.v1.SandboxWorkloadTemplateSpec + 36, // 25: openshell.v1.SandboxWorkloadTemplateSpec.workload:type_name -> openshell.v1.SandboxWorkloadConfig + 257, // 26: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct + 38, // 27: openshell.v1.SandboxWorkloadTemplateSpec.desired_service_level:type_name -> openshell.v1.SandboxServiceLevel + 237, // 28: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + 37, // 29: openshell.v1.SandboxWorkloadConfig.resources:type_name -> openshell.v1.SandboxResources + 32, // 30: openshell.v1.SandboxResources.gpu:type_name -> openshell.v1.GpuResourceRequirements + 39, // 31: openshell.v1.SandboxServiceLevel.startup:type_name -> openshell.v1.SandboxStartup + 258, // 32: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration + 42, // 33: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition 0, // 34: openshell.v1.SandboxStatus.phase:type_name -> openshell.v1.SandboxPhase - 233, // 35: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry - 28, // 36: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec - 234, // 37: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry - 235, // 38: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry - 254, // 39: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 32, // 40: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate - 254, // 41: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 42: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 43: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 44: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 32, // 45: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate - 32, // 46: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate - 254, // 47: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 48: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 49: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 50: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 51: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 52: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 53: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 54: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 55: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 27, // 56: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox - 27, // 57: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox - 255, // 58: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 27, // 59: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 27, // 60: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 254, // 61: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 62: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 63: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 75, // 64: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse - 254, // 65: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 250, // 66: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 74, // 67: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint - 236, // 68: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry - 79, // 69: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout - 80, // 70: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr - 81, // 71: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit - 180, // 72: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget - 181, // 73: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget - 83, // 74: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit - 78, // 75: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest - 86, // 76: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize - 250, // 77: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 27, // 78: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox - 90, // 79: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine - 41, // 80: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent - 91, // 81: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning - 191, // 82: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate - 237, // 83: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry - 255, // 84: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 254, // 85: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 86: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 87: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 255, // 88: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 238, // 89: openshell.v1.UpdateProviderRequest.credential_expires_at_ms:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry - 254, // 90: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 91: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 255, // 92: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider - 255, // 93: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 120, // 94: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile - 103, // 95: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride + 238, // 35: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry + 30, // 36: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec + 239, // 37: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry + 240, // 38: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry + 259, // 39: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 34, // 40: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate + 259, // 41: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 42: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 43: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 44: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 34, // 45: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate + 34, // 46: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate + 259, // 47: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 48: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 49: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 50: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 51: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 52: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 53: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 54: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 55: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 29, // 56: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox + 29, // 57: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox + 260, // 58: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 29, // 59: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 29, // 60: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 259, // 61: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 62: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 63: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 77, // 64: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse + 259, // 65: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 255, // 66: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 76, // 67: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint + 241, // 68: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry + 81, // 69: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout + 82, // 70: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr + 83, // 71: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit + 185, // 72: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget + 186, // 73: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget + 85, // 74: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit + 80, // 75: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest + 88, // 76: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize + 255, // 77: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 29, // 78: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox + 92, // 79: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine + 43, // 80: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent + 93, // 81: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning + 196, // 82: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate + 242, // 83: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry + 260, // 84: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 259, // 85: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 86: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 87: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 260, // 88: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 243, // 89: openshell.v1.UpdateProviderRequest.credential_expires_at_ms:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry + 259, // 90: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 91: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 260, // 92: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider + 260, // 93: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 122, // 94: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile + 105, // 95: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride 1, // 96: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType - 104, // 97: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken - 109, // 98: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh - 105, // 99: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant + 106, // 97: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken + 111, // 98: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh + 107, // 99: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant 2, // 100: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 107, // 101: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial - 108, // 102: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput + 109, // 101: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial + 110, // 102: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput 2, // 103: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 10, // 104: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 254, // 105: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 110, // 106: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 12, // 104: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction + 259, // 105: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 112, // 106: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus 2, // 107: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 239, // 108: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - 254, // 109: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 110, // 110: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 254, // 111: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 110, // 112: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 254, // 113: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 244, // 108: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + 259, // 109: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 112, // 110: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 259, // 111: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 112, // 112: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 259, // 113: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 3, // 114: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory - 106, // 115: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential - 256, // 116: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint - 257, // 117: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 111, // 118: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery - 240, // 119: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry - 120, // 120: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile - 120, // 121: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 101, // 122: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 102, // 123: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 120, // 124: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 101, // 125: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem - 102, // 126: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 120, // 127: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile - 101, // 128: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 102, // 129: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 133, // 130: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding - 241, // 131: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - 242, // 132: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expires_at_ms:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry - 243, // 133: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - 244, // 134: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + 108, // 115: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential + 261, // 116: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 262, // 117: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 113, // 118: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery + 245, // 119: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry + 122, // 120: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile + 122, // 121: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 103, // 122: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 104, // 123: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 122, // 124: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 103, // 125: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem + 104, // 126: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 122, // 127: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile + 103, // 128: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 104, // 129: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 135, // 130: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding + 246, // 131: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + 247, // 132: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expires_at_ms:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry + 248, // 133: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + 249, // 134: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry 4, // 135: openshell.v1.ProviderEnvironmentValue.classification:type_name -> openshell.v1.ProviderEnvironmentValueClassification - 134, // 136: openshell.v1.ProviderEnvironmentValue.static_credential_binding:type_name -> openshell.v1.StaticCredentialBinding - 136, // 137: openshell.v1.ProviderEnvironmentSnapshot.values:type_name -> openshell.v1.ProviderEnvironmentValue - 245, // 138: openshell.v1.ProviderEnvironmentSnapshot.dynamic_credentials:type_name -> openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry - 251, // 139: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 258, // 140: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue - 141, // 141: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation - 246, // 142: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry - 254, // 143: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 142, // 144: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule - 143, // 145: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint - 144, // 146: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule - 145, // 147: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules - 146, // 148: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules - 147, // 149: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary - 259, // 150: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 260, // 151: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule - 261, // 152: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule - 247, // 153: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry - 254, // 154: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 155, // 155: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision - 254, // 156: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 155, // 157: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision - 5, // 158: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus - 5, // 159: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus - 251, // 160: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 248, // 161: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry - 254, // 162: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 90, // 163: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine - 90, // 164: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine - 162, // 165: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello - 173, // 166: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat - 184, // 167: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult - 185, // 168: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose - 170, // 169: openshell.v1.SupervisorMessage.config_update_result:type_name -> openshell.v1.ConfigUpdateResult - 171, // 170: openshell.v1.SupervisorMessage.config_bootstrap_result:type_name -> openshell.v1.ConfigBootstrapResult - 163, // 171: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted - 172, // 172: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected - 174, // 173: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat - 179, // 174: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen - 185, // 175: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose - 165, // 176: openshell.v1.GatewayMessage.config_update:type_name -> openshell.v1.ConfigUpdate - 164, // 177: openshell.v1.SessionAccepted.bootstrap:type_name -> openshell.v1.ConfigBootstrap - 262, // 178: openshell.v1.ConfigBootstrap.sandbox_config:type_name -> openshell.sandbox.v1.SandboxConfigSnapshot - 137, // 179: openshell.v1.ConfigBootstrap.provider_environment:type_name -> openshell.v1.ProviderEnvironmentSnapshot - 262, // 180: openshell.v1.ConfigUpdate.sandbox_config:type_name -> openshell.sandbox.v1.SandboxConfigSnapshot - 137, // 181: openshell.v1.ConfigUpdate.provider_environment:type_name -> openshell.v1.ProviderEnvironmentSnapshot - 167, // 182: openshell.v1.ConfigSnapshotRevision.sandbox_config:type_name -> openshell.v1.SandboxConfigRevision - 263, // 183: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource - 6, // 184: openshell.v1.ConfigComponentApplyResult.component:type_name -> openshell.v1.ConfigComponent - 166, // 185: openshell.v1.ConfigComponentApplyResult.requested_revision:type_name -> openshell.v1.ConfigSnapshotRevision - 166, // 186: openshell.v1.ConfigComponentApplyResult.applied_revision:type_name -> openshell.v1.ConfigSnapshotRevision - 7, // 187: openshell.v1.ConfigComponentApplyResult.outcome:type_name -> openshell.v1.ConfigApplyOutcome - 168, // 188: openshell.v1.ConfigComponentApplyResult.failure:type_name -> openshell.v1.ConfigApplyFailure - 169, // 189: openshell.v1.ConfigUpdateResult.result:type_name -> openshell.v1.ConfigComponentApplyResult - 169, // 190: openshell.v1.ConfigBootstrapResult.results:type_name -> openshell.v1.ConfigComponentApplyResult - 180, // 191: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget - 181, // 192: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget - 182, // 193: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit - 186, // 194: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample - 188, // 195: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount - 259, // 196: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 251, // 197: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 251, // 198: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 187, // 199: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary - 190, // 200: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk - 189, // 201: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary - 254, // 202: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 190, // 203: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk - 254, // 204: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 205: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 200, // 206: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval - 254, // 207: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 259, // 208: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 254, // 209: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 210: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 211: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 254, // 212: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 210, // 213: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry - 249, // 214: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry - 264, // 215: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 264, // 216: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 264, // 217: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace - 250, // 218: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 9, // 219: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole - 9, // 220: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole - 220, // 221: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember - 220, // 222: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember - 106, // 223: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 134, // 224: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding - 106, // 225: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 15, // 226: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest - 17, // 227: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest - 19, // 228: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest - 42, // 229: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest - 50, // 230: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest - 52, // 231: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest - 53, // 232: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest - 43, // 233: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest - 44, // 234: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest - 45, // 235: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest - 46, // 236: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest - 54, // 237: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest - 55, // 238: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest - 56, // 239: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest - 57, // 240: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest - 58, // 241: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest - 59, // 242: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest - 66, // 243: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest - 68, // 244: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest - 69, // 245: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest - 70, // 246: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest - 72, // 247: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest - 76, // 248: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest - 78, // 249: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest - 84, // 250: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame - 85, // 251: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput - 92, // 252: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest - 93, // 253: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest - 94, // 254: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest - 99, // 255: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest - 100, // 256: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest - 123, // 257: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest - 125, // 258: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest - 127, // 259: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest - 95, // 260: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest - 112, // 261: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest - 114, // 262: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest - 116, // 263: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest - 118, // 264: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest - 96, // 265: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest - 130, // 266: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest - 265, // 267: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest - 266, // 268: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest - 140, // 269: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest - 149, // 270: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest - 151, // 271: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest - 153, // 272: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest - 132, // 273: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest - 138, // 274: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest - 156, // 275: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest - 157, // 276: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest - 160, // 277: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage - 175, // 278: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest - 177, // 279: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest - 183, // 280: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame - 88, // 281: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest - 192, // 282: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest - 194, // 283: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest - 196, // 284: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest - 198, // 285: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest - 201, // 286: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest - 203, // 287: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest - 205, // 288: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest - 207, // 289: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest - 209, // 290: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest - 11, // 291: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest - 13, // 292: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest - 212, // 293: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest - 214, // 294: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest - 216, // 295: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest - 218, // 296: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest - 221, // 297: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest - 223, // 298: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest - 225, // 299: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest - 16, // 300: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse - 18, // 301: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse - 20, // 302: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse - 60, // 303: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse - 51, // 304: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse - 60, // 305: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse - 61, // 306: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse - 47, // 307: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 47, // 308: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 48, // 309: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse - 49, // 310: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse - 62, // 311: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse - 63, // 312: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse - 64, // 313: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse - 65, // 314: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse - 60, // 315: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse - 60, // 316: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse - 67, // 317: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse - 75, // 318: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse - 75, // 319: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse - 71, // 320: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse - 73, // 321: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse - 77, // 322: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse - 82, // 323: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent - 84, // 324: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame - 82, // 325: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent - 97, // 326: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse - 97, // 327: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse - 98, // 328: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse - 122, // 329: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse - 121, // 330: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse - 124, // 331: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse - 126, // 332: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse - 128, // 333: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse - 97, // 334: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse - 113, // 335: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse - 115, // 336: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse - 117, // 337: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse - 119, // 338: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse - 129, // 339: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse - 131, // 340: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse - 267, // 341: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse - 268, // 342: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse - 148, // 343: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse - 150, // 344: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse - 152, // 345: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse - 154, // 346: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse - 135, // 347: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse - 139, // 348: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse - 159, // 349: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse - 158, // 350: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse - 161, // 351: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage - 176, // 352: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse - 178, // 353: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse - 183, // 354: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame - 89, // 355: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent - 193, // 356: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse - 195, // 357: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse - 197, // 358: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse - 199, // 359: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse - 202, // 360: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse - 204, // 361: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse - 206, // 362: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse - 208, // 363: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse - 211, // 364: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse - 12, // 365: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse - 14, // 366: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse - 213, // 367: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse - 215, // 368: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse - 217, // 369: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse - 219, // 370: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse - 222, // 371: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse - 224, // 372: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse - 226, // 373: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse - 300, // [300:374] is the sub-list for method output_type - 226, // [226:300] is the sub-list for method input_type - 226, // [226:226] is the sub-list for extension type_name - 226, // [226:226] is the sub-list for extension extendee - 0, // [0:226] is the sub-list for field type_name + 136, // 136: openshell.v1.ProviderEnvironmentValue.static_credential_binding:type_name -> openshell.v1.StaticCredentialBinding + 138, // 137: openshell.v1.ProviderEnvironmentSnapshot.values:type_name -> openshell.v1.ProviderEnvironmentValue + 250, // 138: openshell.v1.ProviderEnvironmentSnapshot.dynamic_credentials:type_name -> openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry + 256, // 139: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 263, // 140: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue + 146, // 141: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation + 251, // 142: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry + 259, // 143: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 5, // 144: openshell.v1.UpdateConfigRequest.consistency:type_name -> openshell.v1.ConfigUpdateConsistency + 8, // 145: openshell.v1.ConfigUpdateOperation.component:type_name -> openshell.v1.ConfigComponent + 171, // 146: openshell.v1.ConfigUpdateOperation.target_revision:type_name -> openshell.v1.ConfigSnapshotRevision + 6, // 147: openshell.v1.ConfigUpdateOperation.state:type_name -> openshell.v1.ConfigUpdateOperationState + 9, // 148: openshell.v1.ConfigUpdateOperation.outcome:type_name -> openshell.v1.ConfigApplyOutcome + 259, // 149: openshell.v1.GetConfigUpdateOperationRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 143, // 150: openshell.v1.GetConfigUpdateOperationResponse.operation:type_name -> openshell.v1.ConfigUpdateOperation + 147, // 151: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule + 148, // 152: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint + 149, // 153: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule + 150, // 154: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules + 151, // 155: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules + 152, // 156: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary + 264, // 157: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 265, // 158: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 266, // 159: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule + 252, // 160: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry + 143, // 161: openshell.v1.UpdateConfigResponse.operation:type_name -> openshell.v1.ConfigUpdateOperation + 259, // 162: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 160, // 163: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision + 259, // 164: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 160, // 165: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision + 7, // 166: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus + 7, // 167: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus + 256, // 168: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 253, // 169: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry + 259, // 170: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 92, // 171: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine + 92, // 172: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine + 167, // 173: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello + 178, // 174: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat + 189, // 175: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult + 190, // 176: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose + 175, // 177: openshell.v1.SupervisorMessage.config_update_result:type_name -> openshell.v1.ConfigUpdateResult + 176, // 178: openshell.v1.SupervisorMessage.config_bootstrap_result:type_name -> openshell.v1.ConfigBootstrapResult + 168, // 179: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted + 177, // 180: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected + 179, // 181: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat + 184, // 182: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen + 190, // 183: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose + 170, // 184: openshell.v1.GatewayMessage.config_update:type_name -> openshell.v1.ConfigUpdate + 169, // 185: openshell.v1.SessionAccepted.bootstrap:type_name -> openshell.v1.ConfigBootstrap + 267, // 186: openshell.v1.ConfigBootstrap.sandbox_config:type_name -> openshell.sandbox.v1.SandboxConfigSnapshot + 139, // 187: openshell.v1.ConfigBootstrap.provider_environment:type_name -> openshell.v1.ProviderEnvironmentSnapshot + 267, // 188: openshell.v1.ConfigUpdate.sandbox_config:type_name -> openshell.sandbox.v1.SandboxConfigSnapshot + 139, // 189: openshell.v1.ConfigUpdate.provider_environment:type_name -> openshell.v1.ProviderEnvironmentSnapshot + 172, // 190: openshell.v1.ConfigSnapshotRevision.sandbox_config:type_name -> openshell.v1.SandboxConfigRevision + 268, // 191: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource + 8, // 192: openshell.v1.ConfigComponentApplyResult.component:type_name -> openshell.v1.ConfigComponent + 171, // 193: openshell.v1.ConfigComponentApplyResult.requested_revision:type_name -> openshell.v1.ConfigSnapshotRevision + 171, // 194: openshell.v1.ConfigComponentApplyResult.applied_revision:type_name -> openshell.v1.ConfigSnapshotRevision + 9, // 195: openshell.v1.ConfigComponentApplyResult.outcome:type_name -> openshell.v1.ConfigApplyOutcome + 173, // 196: openshell.v1.ConfigComponentApplyResult.failure:type_name -> openshell.v1.ConfigApplyFailure + 174, // 197: openshell.v1.ConfigUpdateResult.result:type_name -> openshell.v1.ConfigComponentApplyResult + 174, // 198: openshell.v1.ConfigBootstrapResult.results:type_name -> openshell.v1.ConfigComponentApplyResult + 185, // 199: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget + 186, // 200: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget + 187, // 201: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit + 191, // 202: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample + 193, // 203: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount + 264, // 204: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 256, // 205: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 256, // 206: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 192, // 207: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary + 195, // 208: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk + 194, // 209: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary + 259, // 210: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 195, // 211: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk + 259, // 212: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 213: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 205, // 214: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval + 259, // 215: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 264, // 216: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 259, // 217: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 218: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 219: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 220: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 215, // 221: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry + 254, // 222: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry + 269, // 223: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 269, // 224: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 269, // 225: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace + 255, // 226: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 11, // 227: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole + 11, // 228: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole + 225, // 229: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember + 225, // 230: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember + 108, // 231: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 136, // 232: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding + 108, // 233: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 17, // 234: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest + 19, // 235: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest + 21, // 236: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest + 44, // 237: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest + 52, // 238: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest + 54, // 239: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest + 55, // 240: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest + 45, // 241: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest + 46, // 242: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest + 47, // 243: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest + 48, // 244: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest + 56, // 245: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest + 57, // 246: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest + 58, // 247: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest + 59, // 248: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest + 60, // 249: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest + 61, // 250: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest + 68, // 251: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest + 70, // 252: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest + 71, // 253: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest + 72, // 254: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest + 74, // 255: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest + 78, // 256: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest + 80, // 257: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest + 86, // 258: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame + 87, // 259: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput + 94, // 260: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest + 95, // 261: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest + 96, // 262: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest + 101, // 263: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest + 102, // 264: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest + 125, // 265: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest + 127, // 266: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest + 129, // 267: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest + 97, // 268: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest + 114, // 269: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest + 116, // 270: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest + 118, // 271: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest + 120, // 272: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest + 98, // 273: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest + 132, // 274: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest + 270, // 275: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest + 271, // 276: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest + 142, // 277: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest + 144, // 278: openshell.v1.OpenShell.GetConfigUpdateOperation:input_type -> openshell.v1.GetConfigUpdateOperationRequest + 154, // 279: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest + 156, // 280: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest + 158, // 281: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest + 140, // 282: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest + 161, // 283: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest + 162, // 284: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest + 165, // 285: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage + 180, // 286: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest + 182, // 287: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest + 188, // 288: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame + 90, // 289: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest + 197, // 290: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest + 199, // 291: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest + 201, // 292: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest + 203, // 293: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest + 206, // 294: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest + 208, // 295: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest + 210, // 296: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest + 212, // 297: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest + 214, // 298: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest + 13, // 299: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest + 15, // 300: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest + 217, // 301: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest + 219, // 302: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest + 221, // 303: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest + 223, // 304: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest + 226, // 305: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest + 228, // 306: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest + 230, // 307: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest + 18, // 308: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse + 20, // 309: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse + 22, // 310: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse + 62, // 311: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse + 53, // 312: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse + 62, // 313: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse + 63, // 314: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse + 49, // 315: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 49, // 316: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 50, // 317: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse + 51, // 318: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse + 64, // 319: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse + 65, // 320: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse + 66, // 321: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse + 67, // 322: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse + 62, // 323: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse + 62, // 324: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse + 69, // 325: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse + 77, // 326: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse + 77, // 327: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse + 73, // 328: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse + 75, // 329: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse + 79, // 330: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse + 84, // 331: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent + 86, // 332: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame + 84, // 333: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent + 99, // 334: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse + 99, // 335: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse + 100, // 336: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse + 124, // 337: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse + 123, // 338: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse + 126, // 339: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse + 128, // 340: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse + 130, // 341: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse + 99, // 342: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse + 115, // 343: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse + 117, // 344: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse + 119, // 345: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse + 121, // 346: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse + 131, // 347: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse + 133, // 348: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse + 272, // 349: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse + 273, // 350: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse + 153, // 351: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse + 145, // 352: openshell.v1.OpenShell.GetConfigUpdateOperation:output_type -> openshell.v1.GetConfigUpdateOperationResponse + 155, // 353: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse + 157, // 354: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse + 159, // 355: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse + 141, // 356: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse + 164, // 357: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse + 163, // 358: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse + 166, // 359: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage + 181, // 360: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse + 183, // 361: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse + 188, // 362: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame + 91, // 363: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent + 198, // 364: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse + 200, // 365: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse + 202, // 366: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse + 204, // 367: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse + 207, // 368: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse + 209, // 369: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse + 211, // 370: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse + 213, // 371: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse + 216, // 372: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse + 14, // 373: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse + 16, // 374: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse + 218, // 375: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse + 220, // 376: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse + 222, // 377: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse + 224, // 378: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse + 227, // 379: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse + 229, // 380: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse + 231, // 381: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse + 308, // [308:382] is the sub-list for method output_type + 234, // [234:308] is the sub-list for method input_type + 234, // [234:234] is the sub-list for extension type_name + 234, // [234:234] is the sub-list for extension extendee + 0, // [0:234] is the sub-list for field type_name } func init() { file_openshell_proto_init() } @@ -17269,7 +17691,7 @@ func file_openshell_proto_init() { } file_openshell_proto_msgTypes[103].OneofWrappers = []any{} file_openshell_proto_msgTypes[125].OneofWrappers = []any{} - file_openshell_proto_msgTypes[130].OneofWrappers = []any{ + file_openshell_proto_msgTypes[133].OneofWrappers = []any{ (*PolicyMergeOperation_AddRule)(nil), (*PolicyMergeOperation_RemoveEndpoint)(nil), (*PolicyMergeOperation_RemoveRule)(nil), @@ -17277,7 +17699,7 @@ func file_openshell_proto_init() { (*PolicyMergeOperation_AddAllowRules)(nil), (*PolicyMergeOperation_RemoveBinary)(nil), } - file_openshell_proto_msgTypes[149].OneofWrappers = []any{ + file_openshell_proto_msgTypes[152].OneofWrappers = []any{ (*SupervisorMessage_Hello)(nil), (*SupervisorMessage_Heartbeat)(nil), (*SupervisorMessage_RelayOpenResult)(nil), @@ -17285,7 +17707,7 @@ func file_openshell_proto_init() { (*SupervisorMessage_ConfigUpdateResult)(nil), (*SupervisorMessage_ConfigBootstrapResult)(nil), } - file_openshell_proto_msgTypes[150].OneofWrappers = []any{ + file_openshell_proto_msgTypes[153].OneofWrappers = []any{ (*GatewayMessage_SessionAccepted)(nil), (*GatewayMessage_SessionRejected)(nil), (*GatewayMessage_Heartbeat)(nil), @@ -17293,19 +17715,19 @@ func file_openshell_proto_init() { (*GatewayMessage_RelayClose)(nil), (*GatewayMessage_ConfigUpdate)(nil), } - file_openshell_proto_msgTypes[154].OneofWrappers = []any{ + file_openshell_proto_msgTypes[157].OneofWrappers = []any{ (*ConfigUpdate_SandboxConfig)(nil), (*ConfigUpdate_ProviderEnvironment)(nil), } - file_openshell_proto_msgTypes[155].OneofWrappers = []any{ + file_openshell_proto_msgTypes[158].OneofWrappers = []any{ (*ConfigSnapshotRevision_SandboxConfig)(nil), (*ConfigSnapshotRevision_ProviderEnvironment)(nil), } - file_openshell_proto_msgTypes[168].OneofWrappers = []any{ + file_openshell_proto_msgTypes[171].OneofWrappers = []any{ (*RelayOpen_Ssh)(nil), (*RelayOpen_Tcp)(nil), } - file_openshell_proto_msgTypes[172].OneofWrappers = []any{ + file_openshell_proto_msgTypes[175].OneofWrappers = []any{ (*RelayFrame_Init)(nil), (*RelayFrame_Data)(nil), } @@ -17314,8 +17736,8 @@ func file_openshell_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_openshell_proto_rawDesc), len(file_openshell_proto_rawDesc)), - NumEnums: 11, - NumMessages: 239, + NumEnums: 13, + NumMessages: 242, NumExtensions: 0, NumServices: 1, }, diff --git a/sdk/go/proto/openshellv1/openshell_grpc.pb.go b/sdk/go/proto/openshellv1/openshell_grpc.pb.go index 8d68c4bb82..a9662ed3f2 100644 --- a/sdk/go/proto/openshellv1/openshell_grpc.pb.go +++ b/sdk/go/proto/openshellv1/openshell_grpc.pb.go @@ -23,80 +23,80 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - OpenShell_Health_FullMethodName = "/openshell.v1.OpenShell/Health" - OpenShell_GetCurrentUser_FullMethodName = "/openshell.v1.OpenShell/GetCurrentUser" - OpenShell_GetGatewayInfo_FullMethodName = "/openshell.v1.OpenShell/GetGatewayInfo" - OpenShell_CreateSandbox_FullMethodName = "/openshell.v1.OpenShell/CreateSandbox" - OpenShell_BeginRootfsTarStaging_FullMethodName = "/openshell.v1.OpenShell/BeginRootfsTarStaging" - OpenShell_GetSandbox_FullMethodName = "/openshell.v1.OpenShell/GetSandbox" - OpenShell_ListSandboxes_FullMethodName = "/openshell.v1.OpenShell/ListSandboxes" - OpenShell_CreateSandboxTemplate_FullMethodName = "/openshell.v1.OpenShell/CreateSandboxTemplate" - OpenShell_GetSandboxTemplate_FullMethodName = "/openshell.v1.OpenShell/GetSandboxTemplate" - OpenShell_ListSandboxTemplates_FullMethodName = "/openshell.v1.OpenShell/ListSandboxTemplates" - OpenShell_DeleteSandboxTemplate_FullMethodName = "/openshell.v1.OpenShell/DeleteSandboxTemplate" - OpenShell_ListSandboxProviders_FullMethodName = "/openshell.v1.OpenShell/ListSandboxProviders" - OpenShell_AttachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/AttachSandboxProvider" - OpenShell_DetachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/DetachSandboxProvider" - OpenShell_DeleteSandbox_FullMethodName = "/openshell.v1.OpenShell/DeleteSandbox" - OpenShell_StopSandbox_FullMethodName = "/openshell.v1.OpenShell/StopSandbox" - OpenShell_StartSandbox_FullMethodName = "/openshell.v1.OpenShell/StartSandbox" - OpenShell_CreateSshSession_FullMethodName = "/openshell.v1.OpenShell/CreateSshSession" - OpenShell_ExposeService_FullMethodName = "/openshell.v1.OpenShell/ExposeService" - OpenShell_GetService_FullMethodName = "/openshell.v1.OpenShell/GetService" - OpenShell_ListServices_FullMethodName = "/openshell.v1.OpenShell/ListServices" - OpenShell_DeleteService_FullMethodName = "/openshell.v1.OpenShell/DeleteService" - OpenShell_RevokeSshSession_FullMethodName = "/openshell.v1.OpenShell/RevokeSshSession" - OpenShell_ExecSandbox_FullMethodName = "/openshell.v1.OpenShell/ExecSandbox" - OpenShell_ForwardTcp_FullMethodName = "/openshell.v1.OpenShell/ForwardTcp" - OpenShell_ExecSandboxInteractive_FullMethodName = "/openshell.v1.OpenShell/ExecSandboxInteractive" - OpenShell_CreateProvider_FullMethodName = "/openshell.v1.OpenShell/CreateProvider" - OpenShell_GetProvider_FullMethodName = "/openshell.v1.OpenShell/GetProvider" - OpenShell_ListProviders_FullMethodName = "/openshell.v1.OpenShell/ListProviders" - OpenShell_ListProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/ListProviderProfiles" - OpenShell_GetProviderProfile_FullMethodName = "/openshell.v1.OpenShell/GetProviderProfile" - OpenShell_ImportProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/ImportProviderProfiles" - OpenShell_UpdateProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/UpdateProviderProfiles" - OpenShell_LintProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/LintProviderProfiles" - OpenShell_UpdateProvider_FullMethodName = "/openshell.v1.OpenShell/UpdateProvider" - OpenShell_GetProviderRefreshStatus_FullMethodName = "/openshell.v1.OpenShell/GetProviderRefreshStatus" - OpenShell_ConfigureProviderRefresh_FullMethodName = "/openshell.v1.OpenShell/ConfigureProviderRefresh" - OpenShell_RotateProviderCredential_FullMethodName = "/openshell.v1.OpenShell/RotateProviderCredential" - OpenShell_DeleteProviderRefresh_FullMethodName = "/openshell.v1.OpenShell/DeleteProviderRefresh" - OpenShell_DeleteProvider_FullMethodName = "/openshell.v1.OpenShell/DeleteProvider" - OpenShell_DeleteProviderProfile_FullMethodName = "/openshell.v1.OpenShell/DeleteProviderProfile" - OpenShell_GetSandboxConfig_FullMethodName = "/openshell.v1.OpenShell/GetSandboxConfig" - OpenShell_GetGatewayConfig_FullMethodName = "/openshell.v1.OpenShell/GetGatewayConfig" - OpenShell_UpdateConfig_FullMethodName = "/openshell.v1.OpenShell/UpdateConfig" - OpenShell_GetSandboxPolicyStatus_FullMethodName = "/openshell.v1.OpenShell/GetSandboxPolicyStatus" - OpenShell_ListSandboxPolicies_FullMethodName = "/openshell.v1.OpenShell/ListSandboxPolicies" - OpenShell_ReportPolicyStatus_FullMethodName = "/openshell.v1.OpenShell/ReportPolicyStatus" - OpenShell_GetSandboxProviderEnvironment_FullMethodName = "/openshell.v1.OpenShell/GetSandboxProviderEnvironment" - OpenShell_ExchangeProviderSubjectToken_FullMethodName = "/openshell.v1.OpenShell/ExchangeProviderSubjectToken" - OpenShell_GetSandboxLogs_FullMethodName = "/openshell.v1.OpenShell/GetSandboxLogs" - OpenShell_PushSandboxLogs_FullMethodName = "/openshell.v1.OpenShell/PushSandboxLogs" - OpenShell_ConnectSupervisor_FullMethodName = "/openshell.v1.OpenShell/ConnectSupervisor" - OpenShell_ReportMainProcessExit_FullMethodName = "/openshell.v1.OpenShell/ReportMainProcessExit" - OpenShell_FinalizeMainProcessExit_FullMethodName = "/openshell.v1.OpenShell/FinalizeMainProcessExit" - OpenShell_RelayStream_FullMethodName = "/openshell.v1.OpenShell/RelayStream" - OpenShell_WatchSandbox_FullMethodName = "/openshell.v1.OpenShell/WatchSandbox" - OpenShell_SubmitPolicyAnalysis_FullMethodName = "/openshell.v1.OpenShell/SubmitPolicyAnalysis" - OpenShell_GetDraftPolicy_FullMethodName = "/openshell.v1.OpenShell/GetDraftPolicy" - OpenShell_ApproveDraftChunk_FullMethodName = "/openshell.v1.OpenShell/ApproveDraftChunk" - OpenShell_RejectDraftChunk_FullMethodName = "/openshell.v1.OpenShell/RejectDraftChunk" - OpenShell_ApproveAllDraftChunks_FullMethodName = "/openshell.v1.OpenShell/ApproveAllDraftChunks" - OpenShell_EditDraftChunk_FullMethodName = "/openshell.v1.OpenShell/EditDraftChunk" - OpenShell_UndoDraftChunk_FullMethodName = "/openshell.v1.OpenShell/UndoDraftChunk" - OpenShell_ClearDraftChunks_FullMethodName = "/openshell.v1.OpenShell/ClearDraftChunks" - OpenShell_GetDraftHistory_FullMethodName = "/openshell.v1.OpenShell/GetDraftHistory" - OpenShell_IssueSandboxToken_FullMethodName = "/openshell.v1.OpenShell/IssueSandboxToken" - OpenShell_RefreshSandboxToken_FullMethodName = "/openshell.v1.OpenShell/RefreshSandboxToken" - OpenShell_CreateWorkspace_FullMethodName = "/openshell.v1.OpenShell/CreateWorkspace" - OpenShell_GetWorkspace_FullMethodName = "/openshell.v1.OpenShell/GetWorkspace" - OpenShell_ListWorkspaces_FullMethodName = "/openshell.v1.OpenShell/ListWorkspaces" - OpenShell_DeleteWorkspace_FullMethodName = "/openshell.v1.OpenShell/DeleteWorkspace" - OpenShell_AddWorkspaceMember_FullMethodName = "/openshell.v1.OpenShell/AddWorkspaceMember" - OpenShell_RemoveWorkspaceMember_FullMethodName = "/openshell.v1.OpenShell/RemoveWorkspaceMember" - OpenShell_ListWorkspaceMembers_FullMethodName = "/openshell.v1.OpenShell/ListWorkspaceMembers" + OpenShell_Health_FullMethodName = "/openshell.v1.OpenShell/Health" + OpenShell_GetCurrentUser_FullMethodName = "/openshell.v1.OpenShell/GetCurrentUser" + OpenShell_GetGatewayInfo_FullMethodName = "/openshell.v1.OpenShell/GetGatewayInfo" + OpenShell_CreateSandbox_FullMethodName = "/openshell.v1.OpenShell/CreateSandbox" + OpenShell_BeginRootfsTarStaging_FullMethodName = "/openshell.v1.OpenShell/BeginRootfsTarStaging" + OpenShell_GetSandbox_FullMethodName = "/openshell.v1.OpenShell/GetSandbox" + OpenShell_ListSandboxes_FullMethodName = "/openshell.v1.OpenShell/ListSandboxes" + OpenShell_CreateSandboxTemplate_FullMethodName = "/openshell.v1.OpenShell/CreateSandboxTemplate" + OpenShell_GetSandboxTemplate_FullMethodName = "/openshell.v1.OpenShell/GetSandboxTemplate" + OpenShell_ListSandboxTemplates_FullMethodName = "/openshell.v1.OpenShell/ListSandboxTemplates" + OpenShell_DeleteSandboxTemplate_FullMethodName = "/openshell.v1.OpenShell/DeleteSandboxTemplate" + OpenShell_ListSandboxProviders_FullMethodName = "/openshell.v1.OpenShell/ListSandboxProviders" + OpenShell_AttachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/AttachSandboxProvider" + OpenShell_DetachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/DetachSandboxProvider" + OpenShell_DeleteSandbox_FullMethodName = "/openshell.v1.OpenShell/DeleteSandbox" + OpenShell_StopSandbox_FullMethodName = "/openshell.v1.OpenShell/StopSandbox" + OpenShell_StartSandbox_FullMethodName = "/openshell.v1.OpenShell/StartSandbox" + OpenShell_CreateSshSession_FullMethodName = "/openshell.v1.OpenShell/CreateSshSession" + OpenShell_ExposeService_FullMethodName = "/openshell.v1.OpenShell/ExposeService" + OpenShell_GetService_FullMethodName = "/openshell.v1.OpenShell/GetService" + OpenShell_ListServices_FullMethodName = "/openshell.v1.OpenShell/ListServices" + OpenShell_DeleteService_FullMethodName = "/openshell.v1.OpenShell/DeleteService" + OpenShell_RevokeSshSession_FullMethodName = "/openshell.v1.OpenShell/RevokeSshSession" + OpenShell_ExecSandbox_FullMethodName = "/openshell.v1.OpenShell/ExecSandbox" + OpenShell_ForwardTcp_FullMethodName = "/openshell.v1.OpenShell/ForwardTcp" + OpenShell_ExecSandboxInteractive_FullMethodName = "/openshell.v1.OpenShell/ExecSandboxInteractive" + OpenShell_CreateProvider_FullMethodName = "/openshell.v1.OpenShell/CreateProvider" + OpenShell_GetProvider_FullMethodName = "/openshell.v1.OpenShell/GetProvider" + OpenShell_ListProviders_FullMethodName = "/openshell.v1.OpenShell/ListProviders" + OpenShell_ListProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/ListProviderProfiles" + OpenShell_GetProviderProfile_FullMethodName = "/openshell.v1.OpenShell/GetProviderProfile" + OpenShell_ImportProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/ImportProviderProfiles" + OpenShell_UpdateProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/UpdateProviderProfiles" + OpenShell_LintProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/LintProviderProfiles" + OpenShell_UpdateProvider_FullMethodName = "/openshell.v1.OpenShell/UpdateProvider" + OpenShell_GetProviderRefreshStatus_FullMethodName = "/openshell.v1.OpenShell/GetProviderRefreshStatus" + OpenShell_ConfigureProviderRefresh_FullMethodName = "/openshell.v1.OpenShell/ConfigureProviderRefresh" + OpenShell_RotateProviderCredential_FullMethodName = "/openshell.v1.OpenShell/RotateProviderCredential" + OpenShell_DeleteProviderRefresh_FullMethodName = "/openshell.v1.OpenShell/DeleteProviderRefresh" + OpenShell_DeleteProvider_FullMethodName = "/openshell.v1.OpenShell/DeleteProvider" + OpenShell_DeleteProviderProfile_FullMethodName = "/openshell.v1.OpenShell/DeleteProviderProfile" + OpenShell_GetSandboxConfig_FullMethodName = "/openshell.v1.OpenShell/GetSandboxConfig" + OpenShell_GetGatewayConfig_FullMethodName = "/openshell.v1.OpenShell/GetGatewayConfig" + OpenShell_UpdateConfig_FullMethodName = "/openshell.v1.OpenShell/UpdateConfig" + OpenShell_GetConfigUpdateOperation_FullMethodName = "/openshell.v1.OpenShell/GetConfigUpdateOperation" + OpenShell_GetSandboxPolicyStatus_FullMethodName = "/openshell.v1.OpenShell/GetSandboxPolicyStatus" + OpenShell_ListSandboxPolicies_FullMethodName = "/openshell.v1.OpenShell/ListSandboxPolicies" + OpenShell_ReportPolicyStatus_FullMethodName = "/openshell.v1.OpenShell/ReportPolicyStatus" + OpenShell_ExchangeProviderSubjectToken_FullMethodName = "/openshell.v1.OpenShell/ExchangeProviderSubjectToken" + OpenShell_GetSandboxLogs_FullMethodName = "/openshell.v1.OpenShell/GetSandboxLogs" + OpenShell_PushSandboxLogs_FullMethodName = "/openshell.v1.OpenShell/PushSandboxLogs" + OpenShell_ConnectSupervisor_FullMethodName = "/openshell.v1.OpenShell/ConnectSupervisor" + OpenShell_ReportMainProcessExit_FullMethodName = "/openshell.v1.OpenShell/ReportMainProcessExit" + OpenShell_FinalizeMainProcessExit_FullMethodName = "/openshell.v1.OpenShell/FinalizeMainProcessExit" + OpenShell_RelayStream_FullMethodName = "/openshell.v1.OpenShell/RelayStream" + OpenShell_WatchSandbox_FullMethodName = "/openshell.v1.OpenShell/WatchSandbox" + OpenShell_SubmitPolicyAnalysis_FullMethodName = "/openshell.v1.OpenShell/SubmitPolicyAnalysis" + OpenShell_GetDraftPolicy_FullMethodName = "/openshell.v1.OpenShell/GetDraftPolicy" + OpenShell_ApproveDraftChunk_FullMethodName = "/openshell.v1.OpenShell/ApproveDraftChunk" + OpenShell_RejectDraftChunk_FullMethodName = "/openshell.v1.OpenShell/RejectDraftChunk" + OpenShell_ApproveAllDraftChunks_FullMethodName = "/openshell.v1.OpenShell/ApproveAllDraftChunks" + OpenShell_EditDraftChunk_FullMethodName = "/openshell.v1.OpenShell/EditDraftChunk" + OpenShell_UndoDraftChunk_FullMethodName = "/openshell.v1.OpenShell/UndoDraftChunk" + OpenShell_ClearDraftChunks_FullMethodName = "/openshell.v1.OpenShell/ClearDraftChunks" + OpenShell_GetDraftHistory_FullMethodName = "/openshell.v1.OpenShell/GetDraftHistory" + OpenShell_IssueSandboxToken_FullMethodName = "/openshell.v1.OpenShell/IssueSandboxToken" + OpenShell_RefreshSandboxToken_FullMethodName = "/openshell.v1.OpenShell/RefreshSandboxToken" + OpenShell_CreateWorkspace_FullMethodName = "/openshell.v1.OpenShell/CreateWorkspace" + OpenShell_GetWorkspace_FullMethodName = "/openshell.v1.OpenShell/GetWorkspace" + OpenShell_ListWorkspaces_FullMethodName = "/openshell.v1.OpenShell/ListWorkspaces" + OpenShell_DeleteWorkspace_FullMethodName = "/openshell.v1.OpenShell/DeleteWorkspace" + OpenShell_AddWorkspaceMember_FullMethodName = "/openshell.v1.OpenShell/AddWorkspaceMember" + OpenShell_RemoveWorkspaceMember_FullMethodName = "/openshell.v1.OpenShell/RemoveWorkspaceMember" + OpenShell_ListWorkspaceMembers_FullMethodName = "/openshell.v1.OpenShell/ListWorkspaceMembers" ) // OpenShellClient is the client API for OpenShell service. @@ -215,14 +215,14 @@ type OpenShellClient interface { GetGatewayConfig(ctx context.Context, in *sandboxv1.GetGatewayConfigRequest, opts ...grpc.CallOption) (*sandboxv1.GetGatewayConfigResponse, error) // Update settings or policy at sandbox or global scope. UpdateConfig(ctx context.Context, in *UpdateConfigRequest, opts ...grpc.CallOption) (*UpdateConfigResponse, error) + // Get a durable sandbox configuration update operation by id. + GetConfigUpdateOperation(ctx context.Context, in *GetConfigUpdateOperationRequest, opts ...grpc.CallOption) (*GetConfigUpdateOperationResponse, error) // Get the load status of a specific policy version. GetSandboxPolicyStatus(ctx context.Context, in *GetSandboxPolicyStatusRequest, opts ...grpc.CallOption) (*GetSandboxPolicyStatusResponse, error) // List policy history for a sandbox. ListSandboxPolicies(ctx context.Context, in *ListSandboxPoliciesRequest, opts ...grpc.CallOption) (*ListSandboxPoliciesResponse, error) // Report policy load result (called by sandbox after reload attempt). ReportPolicyStatus(ctx context.Context, in *ReportPolicyStatusRequest, opts ...grpc.CallOption) (*ReportPolicyStatusResponse, error) - // Get provider environment for a sandbox (called by sandbox supervisor at startup). - GetSandboxProviderEnvironment(ctx context.Context, in *GetSandboxProviderEnvironmentRequest, opts ...grpc.CallOption) (*GetSandboxProviderEnvironmentResponse, error) // Exchange a stored provider subject token for an intermediate token scoped // to the calling supervisor's SPIFFE identity. ExchangeProviderSubjectToken(ctx context.Context, in *ExchangeProviderSubjectTokenRequest, opts ...grpc.CallOption) (*ExchangeProviderSubjectTokenResponse, error) @@ -772,6 +772,16 @@ func (c *openShellClient) UpdateConfig(ctx context.Context, in *UpdateConfigRequ return out, nil } +func (c *openShellClient) GetConfigUpdateOperation(ctx context.Context, in *GetConfigUpdateOperationRequest, opts ...grpc.CallOption) (*GetConfigUpdateOperationResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetConfigUpdateOperationResponse) + err := c.cc.Invoke(ctx, OpenShell_GetConfigUpdateOperation_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *openShellClient) GetSandboxPolicyStatus(ctx context.Context, in *GetSandboxPolicyStatusRequest, opts ...grpc.CallOption) (*GetSandboxPolicyStatusResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetSandboxPolicyStatusResponse) @@ -802,16 +812,6 @@ func (c *openShellClient) ReportPolicyStatus(ctx context.Context, in *ReportPoli return out, nil } -func (c *openShellClient) GetSandboxProviderEnvironment(ctx context.Context, in *GetSandboxProviderEnvironmentRequest, opts ...grpc.CallOption) (*GetSandboxProviderEnvironmentResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetSandboxProviderEnvironmentResponse) - err := c.cc.Invoke(ctx, OpenShell_GetSandboxProviderEnvironment_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *openShellClient) ExchangeProviderSubjectToken(ctx context.Context, in *ExchangeProviderSubjectTokenRequest, opts ...grpc.CallOption) (*ExchangeProviderSubjectTokenResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ExchangeProviderSubjectTokenResponse) @@ -1206,14 +1206,14 @@ type OpenShellServer interface { GetGatewayConfig(context.Context, *sandboxv1.GetGatewayConfigRequest) (*sandboxv1.GetGatewayConfigResponse, error) // Update settings or policy at sandbox or global scope. UpdateConfig(context.Context, *UpdateConfigRequest) (*UpdateConfigResponse, error) + // Get a durable sandbox configuration update operation by id. + GetConfigUpdateOperation(context.Context, *GetConfigUpdateOperationRequest) (*GetConfigUpdateOperationResponse, error) // Get the load status of a specific policy version. GetSandboxPolicyStatus(context.Context, *GetSandboxPolicyStatusRequest) (*GetSandboxPolicyStatusResponse, error) // List policy history for a sandbox. ListSandboxPolicies(context.Context, *ListSandboxPoliciesRequest) (*ListSandboxPoliciesResponse, error) // Report policy load result (called by sandbox after reload attempt). ReportPolicyStatus(context.Context, *ReportPolicyStatusRequest) (*ReportPolicyStatusResponse, error) - // Get provider environment for a sandbox (called by sandbox supervisor at startup). - GetSandboxProviderEnvironment(context.Context, *GetSandboxProviderEnvironmentRequest) (*GetSandboxProviderEnvironmentResponse, error) // Exchange a stored provider subject token for an intermediate token scoped // to the calling supervisor's SPIFFE identity. ExchangeProviderSubjectToken(context.Context, *ExchangeProviderSubjectTokenRequest) (*ExchangeProviderSubjectTokenResponse, error) @@ -1440,6 +1440,9 @@ func (UnimplementedOpenShellServer) GetGatewayConfig(context.Context, *sandboxv1 func (UnimplementedOpenShellServer) UpdateConfig(context.Context, *UpdateConfigRequest) (*UpdateConfigResponse, error) { return nil, status.Error(codes.Unimplemented, "method UpdateConfig not implemented") } +func (UnimplementedOpenShellServer) GetConfigUpdateOperation(context.Context, *GetConfigUpdateOperationRequest) (*GetConfigUpdateOperationResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetConfigUpdateOperation not implemented") +} func (UnimplementedOpenShellServer) GetSandboxPolicyStatus(context.Context, *GetSandboxPolicyStatusRequest) (*GetSandboxPolicyStatusResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetSandboxPolicyStatus not implemented") } @@ -1449,9 +1452,6 @@ func (UnimplementedOpenShellServer) ListSandboxPolicies(context.Context, *ListSa func (UnimplementedOpenShellServer) ReportPolicyStatus(context.Context, *ReportPolicyStatusRequest) (*ReportPolicyStatusResponse, error) { return nil, status.Error(codes.Unimplemented, "method ReportPolicyStatus not implemented") } -func (UnimplementedOpenShellServer) GetSandboxProviderEnvironment(context.Context, *GetSandboxProviderEnvironmentRequest) (*GetSandboxProviderEnvironmentResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetSandboxProviderEnvironment not implemented") -} func (UnimplementedOpenShellServer) ExchangeProviderSubjectToken(context.Context, *ExchangeProviderSubjectTokenRequest) (*ExchangeProviderSubjectTokenResponse, error) { return nil, status.Error(codes.Unimplemented, "method ExchangeProviderSubjectToken not implemented") } @@ -2314,6 +2314,24 @@ func _OpenShell_UpdateConfig_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } +func _OpenShell_GetConfigUpdateOperation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetConfigUpdateOperationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetConfigUpdateOperation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetConfigUpdateOperation_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetConfigUpdateOperation(ctx, req.(*GetConfigUpdateOperationRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _OpenShell_GetSandboxPolicyStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetSandboxPolicyStatusRequest) if err := dec(in); err != nil { @@ -2368,24 +2386,6 @@ func _OpenShell_ReportPolicyStatus_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _OpenShell_GetSandboxProviderEnvironment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetSandboxProviderEnvironmentRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(OpenShellServer).GetSandboxProviderEnvironment(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: OpenShell_GetSandboxProviderEnvironment_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(OpenShellServer).GetSandboxProviderEnvironment(ctx, req.(*GetSandboxProviderEnvironmentRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _OpenShell_ExchangeProviderSubjectToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ExchangeProviderSubjectTokenRequest) if err := dec(in); err != nil { @@ -2985,6 +2985,10 @@ var OpenShell_ServiceDesc = grpc.ServiceDesc{ MethodName: "UpdateConfig", Handler: _OpenShell_UpdateConfig_Handler, }, + { + MethodName: "GetConfigUpdateOperation", + Handler: _OpenShell_GetConfigUpdateOperation_Handler, + }, { MethodName: "GetSandboxPolicyStatus", Handler: _OpenShell_GetSandboxPolicyStatus_Handler, @@ -2997,10 +3001,6 @@ var OpenShell_ServiceDesc = grpc.ServiceDesc{ MethodName: "ReportPolicyStatus", Handler: _OpenShell_ReportPolicyStatus_Handler, }, - { - MethodName: "GetSandboxProviderEnvironment", - Handler: _OpenShell_GetSandboxProviderEnvironment_Handler, - }, { MethodName: "ExchangeProviderSubjectToken", Handler: _OpenShell_ExchangeProviderSubjectToken_Handler, diff --git a/sdk/go/proto/sandboxv1/sandbox.pb.go b/sdk/go/proto/sandboxv1/sandbox.pb.go index b9878c1c7d..5d2facffbe 100644 --- a/sdk/go/proto/sandboxv1/sandbox.pb.go +++ b/sdk/go/proto/sandboxv1/sandbox.pb.go @@ -1804,8 +1804,10 @@ type SandboxConfigSnapshot struct { Workspace string `protobuf:"bytes,10,opt,name=workspace,proto3" json:"workspace,omitempty"` PolicyValidationFailureMode string `protobuf:"bytes,11,opt,name=policy_validation_failure_mode,json=policyValidationFailureMode,proto3" json:"policy_validation_failure_mode,omitempty"` ExtensionAuthenticationEnabled bool `protobuf:"varint,12,opt,name=extension_authentication_enabled,json=extensionAuthenticationEnabled,proto3" json:"extension_authentication_enabled,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Monotonic revision of the sandbox-scoped settings row. + SettingsRevision uint64 `protobuf:"varint,13,opt,name=settings_revision,json=settingsRevision,proto3" json:"settings_revision,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SandboxConfigSnapshot) Reset() { @@ -1922,6 +1924,13 @@ func (x *SandboxConfigSnapshot) GetExtensionAuthenticationEnabled() bool { return false } +func (x *SandboxConfigSnapshot) GetSettingsRevision() uint64 { + if x != nil { + return x.SettingsRevision + } + return 0 +} + // Response containing effective sandbox settings and policy. type GetSandboxConfigResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1958,8 +1967,10 @@ type GetSandboxConfigResponse struct { // False also covers older gateways that do not advertise this capability; // supervisors preserve their legacy unauthenticated connection behavior. ExtensionAuthenticationEnabled bool `protobuf:"varint,12,opt,name=extension_authentication_enabled,json=extensionAuthenticationEnabled,proto3" json:"extension_authentication_enabled,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Monotonic revision of the sandbox-scoped settings row. + SettingsRevision uint64 `protobuf:"varint,13,opt,name=settings_revision,json=settingsRevision,proto3" json:"settings_revision,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetSandboxConfigResponse) Reset() { @@ -2076,6 +2087,13 @@ func (x *GetSandboxConfigResponse) GetExtensionAuthenticationEnabled() bool { return false } +func (x *GetSandboxConfigResponse) GetSettingsRevision() uint64 { + if x != nil { + return x.SettingsRevision + } + return 0 +} + // Connection details for one operator-registered supervisor middleware service. // V1 supports plaintext and server-authenticated TLS gRPC. type SupervisorMiddlewareService struct { @@ -2340,7 +2358,7 @@ const file_sandbox_proto_rawDesc = "" + "\x05value\"\x86\x01\n" + "\x10EffectiveSetting\x128\n" + "\x05value\x18\x01 \x01(\v2\".openshell.sandbox.v1.SettingValueR\x05value\x128\n" + - "\x05scope\x18\x02 \x01(\x0e2\".openshell.sandbox.v1.SettingScopeR\x05scope\"\xcb\x06\n" + + "\x05scope\x18\x02 \x01(\x0e2\".openshell.sandbox.v1.SettingScopeR\x05scope\"\xf8\x06\n" + "\x15SandboxConfigSnapshot\x12;\n" + "\x06policy\x18\x01 \x01(\v2#.openshell.sandbox.v1.SandboxPolicyR\x06policy\x12\x18\n" + "\aversion\x18\x02 \x01(\rR\aversion\x12\x1f\n" + @@ -2355,10 +2373,11 @@ const file_sandbox_proto_rawDesc = "" + "\tworkspace\x18\n" + " \x01(\tR\tworkspace\x12C\n" + "\x1epolicy_validation_failure_mode\x18\v \x01(\tR\x1bpolicyValidationFailureMode\x12H\n" + - " extension_authentication_enabled\x18\f \x01(\bR\x1eextensionAuthenticationEnabled\x1ac\n" + + " extension_authentication_enabled\x18\f \x01(\bR\x1eextensionAuthenticationEnabled\x12+\n" + + "\x11settings_revision\x18\r \x01(\x04R\x10settingsRevision\x1ac\n" + "\rSettingsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12<\n" + - "\x05value\x18\x02 \x01(\v2&.openshell.sandbox.v1.EffectiveSettingR\x05value:\x028\x01\"\xd1\x06\n" + + "\x05value\x18\x02 \x01(\v2&.openshell.sandbox.v1.EffectiveSettingR\x05value:\x028\x01\"\xfe\x06\n" + "\x18GetSandboxConfigResponse\x12;\n" + "\x06policy\x18\x01 \x01(\v2#.openshell.sandbox.v1.SandboxPolicyR\x06policy\x12\x18\n" + "\aversion\x18\x02 \x01(\rR\aversion\x12\x1f\n" + @@ -2373,7 +2392,8 @@ const file_sandbox_proto_rawDesc = "" + "\tworkspace\x18\n" + " \x01(\tR\tworkspace\x12C\n" + "\x1epolicy_validation_failure_mode\x18\v \x01(\tR\x1bpolicyValidationFailureMode\x12H\n" + - " extension_authentication_enabled\x18\f \x01(\bR\x1eextensionAuthenticationEnabled\x1ac\n" + + " extension_authentication_enabled\x18\f \x01(\bR\x1eextensionAuthenticationEnabled\x12+\n" + + "\x11settings_revision\x18\r \x01(\x04R\x10settingsRevision\x1ac\n" + "\rSettingsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12<\n" + "\x05value\x18\x02 \x01(\v2&.openshell.sandbox.v1.EffectiveSettingR\x05value:\x028\x01\"\x99\x02\n" + diff --git a/sdk/typescript/src/client.test.ts b/sdk/typescript/src/client.test.ts index d440c75108..8b86e21bb3 100644 --- a/sdk/typescript/src/client.test.ts +++ b/sdk/typescript/src/client.test.ts @@ -22,7 +22,13 @@ import { SCOPE_NAMES, STATUS_NAMES, } from './client.js'; -import { OpenShell, SandboxPhase, ServiceStatus } from './gen/openshell_pb.js'; +import { + ConfigUpdateConsistency, + ConfigUpdateOperationState, + OpenShell, + SandboxPhase, + ServiceStatus, +} from './gen/openshell_pb.js'; import { PolicySource, SettingScope } from './gen/sandbox_pb.js'; function client(impl: Partial>): SandboxClient { @@ -1005,14 +1011,14 @@ describe('config / policy', () => { }); }); - it('setPolicy sends global=false + version pin and (wait) polls until the hash matches', async () => { + it('setPolicy asks the gateway to wait for the durable apply operation', async () => { let updateReq: { name?: string; global?: boolean; expectedResourceVersion?: bigint; policy?: unknown; + consistency?: ConfigUpdateConsistency; } = {}; - let configCalls = 0; const sandbox = client({ getSandbox: () => readySandbox('sb', 'sb-id'), updateConfig: (req) => { @@ -1022,20 +1028,10 @@ describe('config / policy', () => { policyHash: 'target', settingsRevision: 10n, deleted: false, - }; - }, - getSandboxConfig: () => { - configCalls += 1; - const policyHash = configCalls >= 2 ? 'target' : 'stale'; - return { - policy: { version: 1, networkPolicies: {} }, - version: 5, - policyHash, - settings: {}, - configRevision: 1n, - policySource: PolicySource.SANDBOX, - globalPolicyVersion: 0, - providerEnvRevision: 0n, + operation: { + operationId: 'operation-1', + state: ConfigUpdateOperationState.APPLIED, + }, }; }, }); @@ -1052,32 +1048,34 @@ describe('config / policy', () => { expect(updateReq.global).toBe(false); expect(updateReq.expectedResourceVersion).toBe(7n); expect(updateReq.policy).toBeDefined(); + expect(updateReq.consistency).toBe(ConfigUpdateConsistency.WAIT_FOR_APPLY); expect(result.version).toBe(5); expect(result.policyHash).toBe('target'); expect(result.settingsRevision).toBe('10'); - expect(configCalls).toBeGreaterThanOrEqual(2); + expect(result.operationId).toBe('operation-1'); + expect(result.operationState).toBe(ConfigUpdateOperationState.APPLIED); }); - // Fix #4 residual: setPolicy(..., {wait:true}) must not hang forever when the - // getConfig poll stalls. Each poll RPC is bounded by the remaining deadline, - // so a getSandboxConfig that never settles on its own is aborted and the wait - // rejects instead of pending forever. The handler resolves only on the call - // signal firing, proving the per-poll deadline (not the sleep loop) is what - // bounds the returned promise. - it('setPolicy wait rejects when the config poll stalls past the deadline', async () => { + it('setPolicy rejects a terminal failed durable operation', async () => { const sandbox = client({ getSandbox: () => readySandbox('sb', 'sb-id'), - updateConfig: () => ({ version: 5, policyHash: 'target', settingsRevision: 10n, deleted: false }), - getSandboxConfig: (_req, ctx) => - new Promise((_resolve, reject) => { - ctx.signal.addEventListener('abort', () => reject(new Error('aborted')), { once: true }); - }), + updateConfig: () => ({ + version: 5, + policyHash: 'target', + settingsRevision: 10n, + deleted: false, + operation: { + operationId: 'operation-2', + state: ConfigUpdateOperationState.FAILED, + sanitizedError: 'runtime rejected policy', + }, + }), }); - await expect( - sandbox.setPolicy('sb', { version: 1, networkPolicies: {} }, { wait: true, waitTimeoutSecs: 0.2 }), - ).rejects.toMatchObject({ code: 'connect' }); - }, 5000); + await expect(sandbox.setPolicy('sb', { version: 1, networkPolicies: {} }, { wait: true })).rejects.toThrow( + /runtime rejected policy/, + ); + }); it('setSetting upserts a single sandbox-scoped setting (global=false)', async () => { let req: { diff --git a/sdk/typescript/src/client.ts b/sdk/typescript/src/client.ts index 37f145c4c3..3080f690f2 100644 --- a/sdk/typescript/src/client.ts +++ b/sdk/typescript/src/client.ts @@ -19,6 +19,8 @@ import { errorCode, fromConnect, SdkError } from './errors.js'; import type { Provider, WorkspaceSelectorSchema } from './gen/datamodel_pb.js'; import type { Sandbox, SandboxWorkloadTemplate, UpdateConfigResponse } from './gen/openshell_pb.js'; import { + ConfigUpdateConsistency, + ConfigUpdateOperationState, type ExecSandboxInputSchema, OpenShell, SandboxPhase, @@ -342,9 +344,9 @@ export interface SandboxConfig { export interface SetPolicyOptions extends SandboxWorkspaceOptions { /** Pin the sandbox resource version for optimistic concurrency (u64 as string). */ expectedResourceVersion?: string; - /** Poll getConfig until the applied policy hash is observed. */ + /** Ask the gateway to wait for a durable terminal apply result. */ wait?: boolean; - /** Bound the `wait` poll (seconds). Default 60. */ + /** Bound the server-side wait in seconds. Default 60. */ waitTimeoutSecs?: number; } @@ -354,6 +356,8 @@ export interface UpdateConfigResult { /** u64 rendered as a string. */ settingsRevision: string; deleted: boolean; + operationId?: string; + operationState?: ConfigUpdateOperationState; } // ---- enum → lowercase string ----------------------------------------------- @@ -472,6 +476,12 @@ function updateConfigResult(resp: UpdateConfigResponse): UpdateConfigResult { policyHash: resp.policyHash, settingsRevision: resp.settingsRevision.toString(), deleted: resp.deleted, + ...(resp.operation + ? { + operationId: resp.operation.operationId, + operationState: resp.operation.state, + } + : {}), }; } @@ -1426,8 +1436,8 @@ export class SandboxClient { // Update the sandbox-scoped policy. Sandbox scope (global=false) may only // change network_policies; static fields must match the create-time policy or - // the gateway rejects the update. With `wait`, poll getConfig until the - // applied policy hash is observed. + // the gateway rejects the update. With `wait`, the gateway owns the durable + // wait and returns only after a terminal apply result. async setPolicy( name: string, policy: MessageInitShape, @@ -1440,10 +1450,26 @@ export class SandboxClient { global: false, expectedResourceVersion: versionPin(options?.expectedResourceVersion), workspaceScope: workspaceScope(options), + consistency: options?.wait ? ConfigUpdateConsistency.WAIT_FOR_APPLY : ConfigUpdateConsistency.COMMIT_ONLY, + waitTimeoutSecs: Math.max(0, Math.floor(options?.waitTimeoutSecs ?? 60)), }); const result = updateConfigResult(resp); - if (options?.wait) - await this.waitForPolicyHash(name, result.policyHash, options.waitTimeoutSecs, options.workspace); + if (options?.wait) { + if (!resp.operation) throw new SdkError('rpc', 'gateway omitted the requested apply operation'); + if ( + resp.operation.state === ConfigUpdateOperationState.FAILED || + resp.operation.state === ConfigUpdateOperationState.SUPERSEDED || + resp.operation.state === ConfigUpdateOperationState.CANCELLED + ) { + throw new SdkError( + 'rpc', + `policy update operation '${resp.operation.operationId}' did not apply: ${resp.operation.sanitizedError}`, + ); + } + if (resp.operation.state === ConfigUpdateOperationState.PENDING) { + throw new SdkError('rpc', `gateway returned pending operation '${resp.operation.operationId}'`); + } + } return result; } catch (e) { throw e instanceof SdkError ? e : fromConnect(e); @@ -1471,37 +1497,6 @@ export class SandboxClient { throw fromConnect(e); } } - - // Poll getConfig until the applied policy hash is observed. Each poll RPC is - // bounded by the remaining deadline (deadlineOptions), so a stalled getConfig - // cannot make the returned promise outlive timeoutSecs. - private async waitForPolicyHash( - name: string, - policyHash: string, - timeoutSecs = 60, - workspace?: string, - ): Promise { - const deadline = Date.now() + timeoutSecs * 1000; - let delay = 100; - for (;;) { - let config: SandboxConfig; - const pollOptions = deadlineOptions(deadline - Date.now()); - try { - config = await this.getConfig(name, { ...pollOptions, workspace }); - } catch (e) { - if (pollOptions.signal?.aborted || Date.now() >= deadline) { - throw new SdkError('connect', `timed out waiting for policy '${policyHash}' on sandbox '${name}'`); - } - throw e instanceof SdkError ? e : fromConnect(e); - } - if (config.policyHash === policyHash) return; - if (Date.now() >= deadline) { - throw new SdkError('connect', `timed out waiting for policy '${policyHash}' on sandbox '${name}'`); - } - await waitSleep(delay, deadline); - delay = Math.min(delay * 2, 2000); - } - } } // ---- The client ------------------------------------------------------------