Files
OpenShell/proto/openshell.proto
Shiju ffb530671f fix(providers): prepare dynamic credentials across HTTP relay paths (#4152)
* fix(providers): bind multi-route grants to admitted endpoints

Carry gateway-derived endpoint owners through policy and credential delivery. Select grants only from owners that admit the current request, refresh provider snapshots per request, and reject superseded installations before forwarding.

Cover persistent routes, overlapping owners, denial, refresh, cache expiry and recovery with focused tests and a Podman regression.

Signed-off-by: Shiju <shiju@nvidia.com>

* refactor(providers): simplify admitted-endpoint grant selection

Share admitted endpoints between forwarding and credential authorization. Select grants from the live snapshot without rebuilding a locked map, reuse profile policy construction, and preserve freshness guards when grants are removed.

Signed-off-by: Shiju <shiju@nvidia.com>

* fix(providers): preserve trusted owners across policy approval

Sanitize authored owner metadata before composing effective policies, preserve derived owners through proposal approval, and normalize advisor endpoint comparisons. Account for gateway-only owner fields in SDK coverage and schema inventory.

Signed-off-by: Shiju <shiju@nvidia.com>

* refactor(providers): share dynamic credential key layout and require a snapshot for admission

Move the endpoint-bound credential key layout into openshell-core. DynamicCredentialKey encodes the key the gateway builds, and the supervisor reads its endpoint selector, credential identity, and revision-scoped form through the same module instead of parsing tab-separated strings in three places. Revision scoping becomes ProviderCredentialSnapshot::scoped_key.

inject_for_admitted_owners now takes the pinned snapshot directly. The L7 admission path never acquires a grant from the live credential map, which production populates only alongside a snapshot. L4 forwarding keeps the selector-only path through inject_if_needed. Both share one acquire-and-rewrite step.

Delete two relay tests whose behavior the Rego admission tests already prove. Add a test that grants for different headers are acquired only for admitting owners.

Signed-off-by: Shiju <shiju@nvidia.com>

* fix(providers): share credential preparation across HTTP relays

Apply endpoint-owned grants after policy and request middleware in REST,
GraphQL, JSON-RPC/MCP and inspected plaintext forwarding. Preserve live
provider and policy checks through guarded writes, reject missing owner
metadata, and reject profiles that cannot inject dynamic HTTP credentials.

Cover production stream dispatch, multiple endpoints, failure handling and
plaintext ownership with focused regressions. Document upgrade ordering.

Closes #3657

Signed-off-by: Shiju <shiju@nvidia.com>

* fix(providers): validate dynamic credential key encoding

Reject control characters at profile import and key construction. Propagate
invalid key errors through gateway environment resolution and reject malformed
key layouts before token acquisition while preserving supported legacy keys.

Consolidate duplicate relay success tests and share forward-proxy setup without
removing ownership, canonicalization, refresh or guarded-write assertions.
Document token-grant endpoint authority.

Signed-off-by: Shiju <shiju@nvidia.com>

---------

Signed-off-by: Shiju <shiju@nvidia.com>
2026-10-07 23:48:40 +00:00

3858 lines
141 KiB
Protocol Buffer

// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
syntax = "proto3";
package openshell.v1;
import "datamodel.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
import "google/protobuf/timestamp.proto";
import "options.proto";
import "sandbox.proto";
// OpenShell service provides sandbox, provider, and runtime management capabilities.
//
// Conventions:
// - This file owns the public API resource model exposed to OpenShell clients.
// - `Sandbox`, `SandboxSpec`, `SandboxStatus`, and `SandboxPhase` are gateway-owned
// public types. Internal compute drivers must not import or return them directly.
// - The gateway translates internal compute-driver observations into these public
// resource messages before persisting or returning them to clients.
service OpenShell {
// Check the health of the service.
rpc Health(HealthRequest) returns (HealthResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "unauthenticated"
};
}
// Return the authenticated caller identity established by the gateway.
rpc GetCurrentUser(GetCurrentUserRequest) returns (GetCurrentUserResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
};
}
// Fetch elevated live gateway runtime metadata.
rpc GetGatewayInfo(GetGatewayInfoRequest) returns (GetGatewayInfoResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "config:read"
global_role: "platform_admin"
};
}
// Create a new sandbox.
rpc CreateSandbox(CreateSandboxRequest) returns (SandboxResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "user"
};
}
// Allocate a gateway-owned staging slot for a local rootfs tar archive.
//
// The gateway creates a request-scoped directory inside the compute driver's
// staging root and returns an opaque single-use token plus the absolute path
// the client must write the archive to. The token is then passed as
// `template.driver_config.<driver>.rootfs_tar_staging_token` on
// CreateSandbox; callers never name a filesystem path themselves. Only a
// client sharing the gateway's filesystem can complete the upload.
rpc BeginRootfsTarStaging(BeginRootfsTarStagingRequest)
returns (BeginRootfsTarStagingResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "user"
};
}
// Fetch a sandbox by name.
rpc GetSandbox(GetSandboxRequest) returns (SandboxResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:read"
workspace_role: "user"
};
}
// List sandboxes.
rpc ListSandboxes(ListSandboxesRequest) returns (ListSandboxesResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:read"
workspace_role: "user"
};
}
// Create a reusable sandbox workload template.
rpc CreateSandboxTemplate(CreateSandboxTemplateRequest)
returns (SandboxTemplateResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "admin"
};
}
// Fetch a reusable sandbox workload template by name.
rpc GetSandboxTemplate(GetSandboxTemplateRequest)
returns (SandboxTemplateResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:read"
workspace_role: "user"
};
}
// List reusable sandbox workload templates.
rpc ListSandboxTemplates(ListSandboxTemplatesRequest)
returns (ListSandboxTemplatesResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:read"
workspace_role: "user"
};
}
// Delete a reusable sandbox workload template by name.
rpc DeleteSandboxTemplate(DeleteSandboxTemplateRequest)
returns (DeleteSandboxTemplateResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "admin"
};
}
// List provider records attached to a sandbox.
rpc ListSandboxProviders(ListSandboxProvidersRequest)
returns (ListSandboxProvidersResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:read"
workspace_role: "user"
};
}
// Attach a provider record to an existing sandbox.
rpc AttachSandboxProvider(AttachSandboxProviderRequest)
returns (AttachSandboxProviderResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "user"
};
}
// Detach a provider record from an existing sandbox.
rpc DetachSandboxProvider(DetachSandboxProviderRequest)
returns (DetachSandboxProviderResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "user"
};
}
// Inspect the installed authority for one sandbox provider mutation.
rpc GetSandboxProviderStatus(GetSandboxProviderStatusRequest)
returns (GetSandboxProviderStatusResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:read"
workspace_role: "user"
};
}
// Delete a sandbox by name.
rpc DeleteSandbox(DeleteSandboxRequest) returns (DeleteSandboxResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "user"
};
}
// Stop a sandbox while retaining its persistent state.
rpc StopSandbox(StopSandboxRequest) returns (SandboxResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "user"
};
}
// Start a previously stopped sandbox.
rpc StartSandbox(StartSandboxRequest) returns (SandboxResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "user"
};
}
// Create a short-lived SSH session for a sandbox.
rpc CreateSshSession(CreateSshSessionRequest) returns (CreateSshSessionResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "user"
};
}
// Create or update a sandbox HTTP service endpoint for local routing.
rpc ExposeService(ExposeServiceRequest) returns (ServiceEndpointResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "user"
};
}
// Fetch one sandbox HTTP service endpoint.
rpc GetService(GetServiceRequest) returns (ServiceEndpointResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:read"
workspace_role: "user"
};
}
// List sandbox HTTP service endpoints.
rpc ListServices(ListServicesRequest) returns (ListServicesResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:read"
workspace_role: "user"
};
}
// Delete one sandbox HTTP service endpoint.
rpc DeleteService(DeleteServiceRequest) returns (DeleteServiceResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "user"
};
}
// Revoke a previously issued SSH session.
rpc RevokeSshSession(RevokeSshSessionRequest) returns (RevokeSshSessionResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "user"
};
}
// Execute a command in a ready sandbox and stream output.
rpc ExecSandbox(ExecSandboxRequest) returns (stream ExecSandboxEvent) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "user"
};
}
// Forward one CLI-side TCP connection to a loopback TCP target in a sandbox.
rpc ForwardTcp(stream TcpForwardFrame) returns (stream TcpForwardFrame) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "user"
};
}
// Execute an interactive command with bidirectional stdin/stdout streaming.
// The first client message MUST carry an ExecSandboxInput with the start
// variant. Subsequent messages carry stdin bytes or window resize events.
rpc ExecSandboxInteractive(stream ExecSandboxInput) returns (stream ExecSandboxEvent) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:write"
workspace_role: "user"
};
}
// Create a provider.
rpc CreateProvider(CreateProviderRequest) returns (ProviderResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:write"
workspace_role: "admin"
};
}
// Fetch a provider by name.
rpc GetProvider(GetProviderRequest) returns (ProviderResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:read"
workspace_role: "user"
};
}
// List providers.
rpc ListProviders(ListProvidersRequest) returns (ListProvidersResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:read"
workspace_role: "user"
};
}
// List available provider type profiles.
rpc ListProviderProfiles(ListProviderProfilesRequest)
returns (ListProviderProfilesResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:read"
workspace_role: "user"
};
}
// Fetch one provider type profile by id.
rpc GetProviderProfile(GetProviderProfileRequest)
returns (ProviderProfileResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:read"
workspace_role: "user"
};
}
// Import custom provider type profiles.
rpc ImportProviderProfiles(ImportProviderProfilesRequest)
returns (ImportProviderProfilesResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:write"
workspace_role: "admin"
};
}
// Update an existing custom provider type profile.
rpc UpdateProviderProfiles(UpdateProviderProfilesRequest)
returns (UpdateProviderProfilesResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:write"
workspace_role: "admin"
};
}
// Validate provider type profiles without registering them.
rpc LintProviderProfiles(LintProviderProfilesRequest)
returns (LintProviderProfilesResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:read"
workspace_role: "user"
};
}
// Update an existing provider by name.
rpc UpdateProvider(UpdateProviderRequest) returns (ProviderResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:write"
workspace_role: "admin"
};
}
// Fetch refresh status for one provider or provider credential.
rpc GetProviderRefreshStatus(GetProviderRefreshStatusRequest)
returns (GetProviderRefreshStatusResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:read"
workspace_role: "user"
};
}
// Configure gateway-owned refresh material for one provider credential.
rpc ConfigureProviderRefresh(ConfigureProviderRefreshRequest)
returns (ConfigureProviderRefreshResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:write"
workspace_role: "admin"
};
}
// Record a gateway-owned refresh request for one provider credential.
rpc RotateProviderCredential(RotateProviderCredentialRequest)
returns (RotateProviderCredentialResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:write"
workspace_role: "admin"
};
}
// Delete gateway-owned refresh configuration for one provider credential.
rpc DeleteProviderRefresh(DeleteProviderRefreshRequest)
returns (DeleteProviderRefreshResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:write"
workspace_role: "admin"
};
}
// Delete a provider by name.
rpc DeleteProvider(DeleteProviderRequest) returns (DeleteProviderResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:write"
workspace_role: "admin"
};
}
// Delete a custom provider type profile by id.
rpc DeleteProviderProfile(DeleteProviderProfileRequest)
returns (DeleteProviderProfileResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "provider:write"
workspace_role: "admin"
};
}
// Get sandbox settings by id (called by sandbox entrypoint and poll loop).
rpc GetSandboxConfig(openshell.sandbox.v1.GetSandboxConfigRequest)
returns (openshell.sandbox.v1.GetSandboxConfigResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "dual"
scope: "config:read"
workspace_role: "user"
};
}
// Get gateway-global settings (read-only runtime configuration; any
// authenticated user may read these without requiring Platform Admin).
//
// Scope-only (no role): scopes are granted by the IdP at token issuance,
// orthogonal to workspace membership. Deployments that enable scope
// enforcement configure the IdP to grant config:read (or openshell:all)
// to all sandbox users, so this does not block least-privilege flows.
rpc GetGatewayConfig(openshell.sandbox.v1.GetGatewayConfigRequest)
returns (openshell.sandbox.v1.GetGatewayConfigResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "config:read"
};
}
// Update settings or policy at sandbox or global scope.
rpc UpdateConfig(UpdateConfigRequest)
returns (UpdateConfigResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "dual"
scope: "config:write"
workspace_role: "admin"
};
}
// Get the load status of a specific policy version.
rpc GetSandboxPolicyStatus(GetSandboxPolicyStatusRequest)
returns (GetSandboxPolicyStatusResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:read"
workspace_role: "user"
};
}
// List policy history for a sandbox.
rpc ListSandboxPolicies(ListSandboxPoliciesRequest)
returns (ListSandboxPoliciesResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:read"
workspace_role: "user"
};
}
// Report policy load result (called by sandbox after reload attempt).
rpc ReportPolicyStatus(ReportPolicyStatusRequest)
returns (ReportPolicyStatusResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "sandbox"
};
}
// Replace the gateway's observed tool server endpoint status for one sandbox.
rpc ReportEndpointStatus(ReportEndpointStatusRequest)
returns (ReportEndpointStatusResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "sandbox"
};
}
// Report installed provider state for the current ConnectSupervisor session.
// Replacing or losing that session invalidates its observations.
rpc ReportProviderReadiness(ReportProviderReadinessRequest)
returns (ReportProviderReadinessResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "sandbox"
};
}
// Register startup and acknowledge an exact validated runtime configuration.
rpc ReportSandboxConfiguration(ReportSandboxConfigurationRequest)
returns (ReportSandboxConfigurationResponse) {
option (openshell.options.v1.authorization) = { auth_mode: "sandbox" };
}
// 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)
returns (ExchangeProviderSubjectTokenResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "sandbox"
};
}
// Fetch recent sandbox logs (one-shot).
rpc GetSandboxLogs(GetSandboxLogsRequest) returns (GetSandboxLogsResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:read"
workspace_role: "user"
};
}
// Push sandbox supervisor logs to the server (client-streaming).
rpc PushSandboxLogs(stream PushSandboxLogsRequest) returns (PushSandboxLogsResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "sandbox"
};
}
// Persistent supervisor-to-gateway session (bidirectional streaming).
//
// The supervisor opens this stream at startup and keeps it alive for the
// sandbox lifetime. The gateway uses it to coordinate relay channels for
// SSH connect, ExecSandbox, and targetable sandbox services. Raw service
// bytes flow over RelayStream calls (separate HTTP/2 streams on the same
// connection), not over this stream.
rpc ConnectSupervisor(stream SupervisorMessage) returns (stream GatewayMessage) {
option (openshell.options.v1.authorization) = {
auth_mode: "sandbox"
};
}
// Persist the canonical main process result before the supervisor exits.
rpc ReportMainProcessExit(ReportMainProcessExitRequest) returns (ReportMainProcessExitResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "sandbox"
};
}
// Confirm that foreground terminal delivery completed naturally.
rpc FinalizeMainProcessExit(FinalizeMainProcessExitRequest)
returns (FinalizeMainProcessExitResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "sandbox"
};
}
// Raw byte relay between supervisor and gateway.
//
// The supervisor initiates this call after receiving a RelayOpen message
// on its ConnectSupervisor stream. The first RelayFrame carries a
// RelayInit with the channel_id to associate the new HTTP/2 stream with
// the pending relay slot on the gateway. Subsequent frames carry raw bytes in either
// direction between the gateway-side waiter (ForwardTcp / exec handler)
// and the supervisor-side target bridge.
//
// This rides the same TCP+TLS+HTTP/2 connection as ConnectSupervisor —
// no new TLS handshake, no reverse HTTP CONNECT.
rpc RelayStream(stream RelayFrame) returns (stream RelayFrame) {
option (openshell.options.v1.authorization) = {
auth_mode: "sandbox"
};
}
// Internal gateway-to-gateway relay forwarding.
//
// A gateway replica that receives a user request for a sandbox whose
// supervisor session is owned by a different replica opens this stream to the
// owner. The first frame carries PeerRelayInit; subsequent frames carry raw
// bytes in either direction. This RPC is authenticated as a gateway peer, not
// as a user or sandbox supervisor.
rpc PeerRelay(stream PeerRelayFrame) returns (stream PeerRelayFrame) {
option (openshell.options.v1.authorization) = {
auth_mode: "peer"
};
}
// Internal gateway-to-owner forwarding for supervisor-bound state.
rpc PeerReportProviderReadiness(ReportProviderReadinessRequest)
returns (ReportProviderReadinessResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "peer"
};
}
rpc PeerReportEndpointStatus(ReportEndpointStatusRequest)
returns (ReportEndpointStatusResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "peer"
};
}
rpc PeerGetSandboxProviderStatus(GetSandboxProviderStatusRequest)
returns (GetSandboxProviderStatusResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "peer"
};
}
// Watch a sandbox and stream updates.
//
// This stream can include:
// - Sandbox status snapshots (phase/status)
// - OpenShell server process logs correlated by sandbox_id
// - Platform events correlated to the sandbox
rpc WatchSandbox(WatchSandboxRequest) returns (stream SandboxStreamEvent) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "sandbox:read"
workspace_role: "user"
};
}
// ---------------------------------------------------------------------------
// Draft policy recommendation RPCs
// ---------------------------------------------------------------------------
// Submit denial analysis results from sandbox (summaries + proposed chunks).
rpc SubmitPolicyAnalysis(SubmitPolicyAnalysisRequest)
returns (SubmitPolicyAnalysisResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "sandbox"
};
}
// Get draft policy recommendations for a sandbox.
rpc GetDraftPolicy(GetDraftPolicyRequest) returns (GetDraftPolicyResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "dual"
scope: "config:read"
workspace_role: "user"
};
}
// Approve a single draft policy chunk (merges into active policy).
rpc ApproveDraftChunk(ApproveDraftChunkRequest)
returns (ApproveDraftChunkResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "config:write"
workspace_role: "admin"
};
}
// Reject a single draft policy chunk.
rpc RejectDraftChunk(RejectDraftChunkRequest)
returns (RejectDraftChunkResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "config:write"
workspace_role: "admin"
};
}
// Approve all pending draft chunks (skips security-flagged unless forced).
rpc ApproveAllDraftChunks(ApproveAllDraftChunksRequest)
returns (ApproveAllDraftChunksResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "config:write"
workspace_role: "admin"
};
}
// Edit a pending draft chunk in-place (e.g. narrow allowed_ips).
rpc EditDraftChunk(EditDraftChunkRequest) returns (EditDraftChunkResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "config:write"
workspace_role: "admin"
};
}
// Reverse an approval (remove merged rule from active policy).
rpc UndoDraftChunk(UndoDraftChunkRequest) returns (UndoDraftChunkResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "config:write"
workspace_role: "admin"
};
}
// Clear all pending draft chunks for a sandbox.
rpc ClearDraftChunks(ClearDraftChunksRequest)
returns (ClearDraftChunksResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "config:write"
workspace_role: "admin"
};
}
// Get decision history for a sandbox's draft policy.
rpc GetDraftHistory(GetDraftHistoryRequest) returns (GetDraftHistoryResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "config:read"
workspace_role: "user"
};
}
// Exchange a sandbox-bootstrap credential (e.g. a Kubernetes projected
// ServiceAccount token) for a gateway-minted JWT bound to the calling
// sandbox's UUID. Used by the Kubernetes driver path; singleplayer
// drivers receive the gateway JWT directly from the create-sandbox flow
// and never call this RPC.
rpc IssueSandboxToken(IssueSandboxTokenRequest) returns (IssueSandboxTokenResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "sandbox"
};
}
// Renew the calling sandbox's gateway JWT. Older tokens remain valid
// until their own expiry; deployments should keep token TTLs short to
// bound replay exposure. The supervisor calls this from a background
// task at ~80% of the token's lifetime; the new token is cached in
// memory only — the on-disk bootstrap file is intentionally not
// rewritten.
rpc RefreshSandboxToken(RefreshSandboxTokenRequest)
returns (RefreshSandboxTokenResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "sandbox"
};
}
// ---------------------------------------------------------------------------
// Workspace management RPCs
// ---------------------------------------------------------------------------
// Create a workspace.
rpc CreateWorkspace(CreateWorkspaceRequest) returns (CreateWorkspaceResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "workspace:write"
global_role: "platform_admin"
};
}
// Fetch a workspace by name.
rpc GetWorkspace(GetWorkspaceRequest) returns (GetWorkspaceResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "workspace:read"
workspace_role: "user"
};
}
// List workspaces.
rpc ListWorkspaces(ListWorkspacesRequest) returns (ListWorkspacesResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "workspace:read"
workspace_role: "user"
};
}
// Delete a workspace by name.
rpc DeleteWorkspace(DeleteWorkspaceRequest) returns (DeleteWorkspaceResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "workspace:write"
global_role: "platform_admin"
};
}
// Add a member to a workspace.
rpc AddWorkspaceMember(AddWorkspaceMemberRequest) returns (AddWorkspaceMemberResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "workspace:write"
workspace_role: "admin"
};
}
// Remove a member from a workspace.
rpc RemoveWorkspaceMember(RemoveWorkspaceMemberRequest) returns (RemoveWorkspaceMemberResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "workspace:write"
workspace_role: "admin"
};
}
// List members of a workspace.
rpc ListWorkspaceMembers(ListWorkspaceMembersRequest) returns (ListWorkspaceMembersResponse) {
option (openshell.options.v1.authorization) = {
auth_mode: "bearer"
scope: "workspace:read"
workspace_role: "user"
};
}
}
// IssueSandboxToken request. Empty body; identity is established by the
// authentication credentials carried in the request headers (a projected
// Kubernetes ServiceAccount JWT in the K8s driver path).
message IssueSandboxTokenRequest {}
// IssueSandboxToken response. The supervisor caches the returned token in
// memory and presents it as `Authorization: Bearer` on every subsequent
// gateway RPC.
message IssueSandboxTokenResponse {
reserved 2;
reserved "expires_at_ms";
// Gateway-minted session JWT bound to the calling sandbox's UUID, active
// runtime generation, authorization epoch, and durable token lineage.
string token = 1 [(openshell.options.v1.secret) = true];
// Absolute expiry of the issued token. Absence means the token is non-expiring.
google.protobuf.Timestamp expiration_time = 102;
}
// RefreshSandboxToken request. The calling principal must already be a
// sandbox principal (i.e. the request carries a still-valid gateway-minted
// JWT in its Authorization header). Extension service names are resolved
// against server-owned registrations and the sandbox's effective policy;
// callers never choose token audiences directly.
message RefreshSandboxTokenRequest {
// Operator registration names for extension services selected by the
// sandbox's effective policy.
repeated string extension_service_names = 1;
}
// RefreshSandboxToken response. The new token replaces the supervisor's
// in-memory bearer credential.
message RefreshSandboxTokenResponse {
reserved 2, 5;
reserved "expires_at_ms", "sandbox_expires_at_ms";
// Fresh gateway-minted JWT bound to the same sandbox UUID.
string token = 1 [(openshell.options.v1.secret) = true];
// Absolute expiry of the new token. Absence means the token is non-expiring.
google.protobuf.Timestamp expiration_time = 102;
// Fresh credentials for the requested, policy-authorized extension
// services. These remain in supervisor memory and are never persisted.
repeated ExtensionServiceCredential extension_credentials = 3;
// Fresh Sandbox Protocol bearer token from the same atomic refresh.
string sandbox_token = 4 [(openshell.options.v1.secret) = true];
// Absolute Sandbox Protocol token expiry. Absence means the token is non-expiring.
google.protobuf.Timestamp sandbox_expiration_time = 105;
// Launch generation to which both refreshed credentials are bound.
string session_id = 6;
// Durable authorization epoch shared by the gateway and Sandbox Runtime.
uint64 credential_epoch = 7;
}
// Health check request.
message HealthRequest {}
// Health check response.
message HealthResponse {
// Service status.
ServiceStatus status = 1;
// Service version.
string version = 2;
}
// Current-user request. The identity comes from the authenticated request.
message GetCurrentUserRequest {}
// Authenticated user identity as validated by the gateway.
message GetCurrentUserResponse {
// Stable identity subject (for example, the OIDC `sub` claim).
string subject = 1;
// Human-readable identity name when supplied by the authentication provider.
string display_name = 2;
// Roles granted to the authenticated identity.
repeated string roles = 3;
// OAuth2 scopes granted to the authenticated identity.
repeated string scopes = 4;
// Authentication provider that established the identity.
string identity_provider = 5;
}
// Gateway info request.
message GetGatewayInfoRequest {}
// Gateway info response.
message GetGatewayInfoResponse {
// Service status.
ServiceStatus status = 1;
// OpenShell gateway binary version.
string gateway_version = 2;
// Compute driver runtimes initialized by this gateway. Current gateways
// return exactly one entry.
repeated ComputeDriverInfo compute_drivers = 3;
// Negotiated non-secret metadata for every initialized extension.
repeated NegotiatedExtensionInfo extensions = 4;
}
enum ExtensionKind {
EXTENSION_KIND_UNSPECIFIED = 0;
EXTENSION_KIND_COMPUTE_DRIVER = 1;
EXTENSION_KIND_CREDENTIAL_DRIVER = 2;
EXTENSION_KIND_GATEWAY_INTERCEPTOR = 3;
EXTENSION_KIND_SUPERVISOR_MIDDLEWARE = 4;
}
// Public, non-secret snapshot of one successful startup negotiation.
message NegotiatedExtensionInfo {
ExtensionKind kind = 1;
// Gateway/operator-selected registration name.
string configured_name = 2;
// Extension-reported implementation identity.
string implementation_name = 3;
// Extension build version, distinct from the protocol version.
string implementation_version = 4;
uint32 protocol_major = 5;
uint32 protocol_minor = 6;
// Extension-supported optional capabilities, sorted for stable output.
repeated string supported_capabilities = 7;
// Capabilities the extension requires from the gateway.
repeated string required_capabilities = 8;
}
// Info for one initialized compute driver runtime.
message ComputeDriverInfo {
// Gateway-selected driver name used for routing and driver_config keys.
string name = 1;
// Capabilities reported by the driver during gateway runtime initialization.
ComputeDriverCapabilities capabilities = 2;
}
// Public compute driver capability snapshot.
message ComputeDriverCapabilities {
// Driver-reported human-readable name from the startup capability snapshot.
string driver_name = 1;
// Driver-reported implementation version from the startup capability snapshot.
string driver_version = 2;
// Static portable resource request forms reported by the driver.
ResourceCapabilities resource_capabilities = 3;
}
// Static portable resource request forms reported by a compute driver.
// An omitted domain means the driver does not report that domain.
message ResourceCapabilities {
CpuResourceCapabilities cpu = 1;
MemoryResourceCapabilities memory = 2;
GpuResourceCapabilities gpu = 3;
}
message CpuResourceCapabilities {
// The driver accepts and enforces a portable CPU limit.
bool limit_supported = 1;
}
message MemoryResourceCapabilities {
// The driver accepts and enforces a portable memory limit.
bool limit_supported = 1;
}
message GpuResourceCapabilities {
// The driver accepts a GPU request with no explicit count.
bool default_selection_supported = 1;
// The driver accepts an explicit `gpu.count` request.
bool count_selection_supported = 2;
}
// Public sandbox resource exposed by the OpenShell API.
//
// This is the canonical gateway-owned view of a sandbox. It merges user intent
// (`spec`) with gateway-managed metadata and status derived from internal
// compute-driver observations.
//
// Note: The `namespace` field has been removed from the public API. It remains
// in the internal `DriverSandbox` message as a compute-driver implementation detail.
message Sandbox {
// Kubernetes-style metadata (id, name, labels, timestamps, resource version).
openshell.datamodel.v1.ObjectMeta metadata = 1;
// Desired sandbox configuration submitted through the API.
SandboxSpec spec = 2;
// Latest user-facing observed status derived by the gateway.
SandboxStatus status = 3;
// Read-only SSH host identity in OpenSSH SHA256:<base64> form. Stable for
// this sandbox's lifetime, including runtime restarts. Empty on legacy
// runtimes that do not provide a gateway-managed SSH identity.
string host_key_fingerprint = 6;
// Read-only provenance for sandboxes created from a reusable workload template.
SandboxWorkloadTemplateProvenance created_from_workload_template = 20;
reserved 4, 5;
reserved "phase", "current_policy_version";
}
// Desired sandbox configuration provided through the public API.
message SandboxSpec {
// Log level exposed to processes running inside the sandbox.
string log_level = 1;
// Environment variables injected into the sandbox runtime.
map<string, string> environment = 5;
// Container or VM template used to provision the sandbox.
SandboxTemplate template = 6;
// Required sandbox policy configuration.
openshell.sandbox.v1.SandboxPolicy policy = 7;
// Provider names to attach to this sandbox.
repeated string providers = 8;
// Portable resource requirements used by the gateway for driver selection
// and by drivers for provisioning.
ResourceRequirements resource_requirements = 9;
reserved 10;
reserved "gpu_device";
// Field 11 was `proposal_approval_mode`. The approval mode is now a
// runtime setting (gateway or sandbox scope) read via UpdateConfig /
// GetSandboxConfig, so it can be flipped on a running sandbox and
// managed fleet-wide.
reserved 11;
reserved "proposal_approval_mode";
// Canonical command launched once by the sandbox supervisor. No shell
// parsing is performed. The gateway normalizes an omitted command to the
// portable scratch login shell before persistence.
repeated string command = 12;
// Allocate a retained pseudo-terminal for the main process.
bool tty = 13;
// Gateway-owned attachment identity, changed atomically with the provider set.
// Equality only: detach and reattach must not revive an older receipt.
string provider_attachment_epoch = 14;
// Gateway-owned policy for replacing the sandbox runtime after the canonical
// main process exits. Unspecified is normalized to Never before persistence.
SandboxRestartPolicy restart_policy = 15;
}
message ResourceRequirements {
// GPU requirements for the sandbox. Presence indicates a GPU request.
GpuResourceRequirements gpu = 1;
}
// Public GPU resource requirements.
message GpuResourceRequirements {
// Optional number of GPUs requested. When omitted, the request is for one
// GPU using the selected driver's default assignment behavior.
optional uint32 count = 1;
}
// Historical inline compute template mapped onto compute-driver template inputs.
//
// Despite its name, this is not a reusable named sandbox template resource. It
// is an inline part of `SandboxSpec` kept for v1 compatibility. A future
// breaking API cleanup may rename this message to free `SandboxTemplate` for
// the reusable template resource now represented by `SandboxWorkloadTemplate`.
message SandboxTemplate {
// Fully-qualified OCI image reference used to boot the sandbox.
string image = 1;
// Optional runtime class name requested from the compute platform.
string runtime_class_name = 2;
// Optional agent socket path exposed to the workload.
string agent_socket = 3;
// Labels applied to compute-platform resources for this sandbox.
map<string, string> labels = 4;
// Annotations applied to compute-platform resources for this sandbox.
map<string, string> annotations = 5;
// Additional environment variables injected by the template.
map<string, string> environment = 6;
// Platform-specific compute resource requirements and limits.
google.protobuf.Struct resources = 7;
reserved 9;
reserved "volume_claim_templates";
// Enable Kubernetes user namespace isolation (hostUsers: false).
// When true, container UID 0 maps to a non-root host UID and capabilities
// become namespaced. Requires Kubernetes 1.33+ with user namespace support
// available (beta through 1.35, GA in 1.36+) and a supporting runtime.
// When unset, the cluster-wide default is used.
optional bool user_namespaces = 10;
// Driver-keyed opaque config envelope supplied by the caller.
// The gateway selects the block matching the active compute driver and
// forwards only that inner Struct to DriverSandboxTemplate.driver_config.
// The selected driver owns nested schema validation.
google.protobuf.Struct driver_config = 11;
}
// Reusable named sandbox workload template resource.
//
// This is the actual workspace-scoped template resource used to create
// sandboxes by reference. It uses the longer name in v1 to avoid colliding with
// the historical inline `SandboxTemplate` message. A future breaking API
// cleanup may rename this resource to `SandboxTemplate`.
message SandboxWorkloadTemplate {
// Kubernetes-style metadata (id, name, labels, timestamps, resource version).
openshell.datamodel.v1.ObjectMeta metadata = 1;
// Desired reusable workload shape and template-owned driver config.
SandboxWorkloadTemplateSpec spec = 2;
}
message SandboxWorkloadTemplateSpec {
// Portable workload shape.
SandboxWorkloadConfig workload = 1;
// Driver-keyed opaque config envelope supplied by the template owner.
google.protobuf.Struct driver_config = 2;
// Desired service level associated with this template.
SandboxServiceLevel desired_service_level = 3;
}
message SandboxWorkloadConfig {
// Fully-qualified OCI image reference used to boot the sandbox.
string image = 1;
// Environment variables injected into the sandbox runtime.
map<string, string> environment = 2;
// Portable resource requirements for sandboxes created from this workload.
SandboxResources resources = 3;
}
message SandboxResources {
// Portable CPU quantity, for example "500m" or "2".
string cpu = 1;
// Portable memory quantity, for example "512Mi" or "2Gi".
string memory = 2;
// GPU requirements for the sandbox workload. Presence indicates a GPU
// request. When count is omitted, the request uses the selected driver's
// default GPU assignment behavior.
GpuResourceRequirements gpu = 3;
}
message SandboxServiceLevel {
SandboxStartup startup = 1;
}
message SandboxStartup {
google.protobuf.Duration ready_within = 1;
uint32 max_burst = 2;
}
message SandboxWorkloadTemplateProvenance {
string name = 1;
string resource_version = 2;
}
// User-facing sandbox status derived by the gateway from compute-driver observations.
//
// Public status does not embed driver-only flags such as `deleting`.
message SandboxStatus {
// Name of the agent pod or equivalent runtime instance.
string agent_pod = 2;
// File descriptor or endpoint for reaching the agent service, when available.
string agent_fd = 3;
// File descriptor or endpoint for reaching the sandbox service, when available.
string sandbox_fd = 4;
// Latest user-facing readiness and lifecycle conditions.
repeated SandboxCondition conditions = 5;
// Gateway-derived lifecycle summary.
SandboxPhase phase = 6;
// Currently active policy version (updated when sandbox reports loaded).
uint32 current_policy_version = 7;
// Supervisor instance currently associated with the canonical main process.
// The gateway uses this to reject stale exit reports after a restart.
string main_process_instance_id = 8;
// Most recent normalized main process result. Signal exits use 128 + signal number.
// Cleared when a replacement main process becomes ready.
optional int32 exit_code = 9;
// Last accepted network result for each configured tool server endpoint.
// Currently populated for MCP-over-HTTP endpoints. These passive results
// remain separate from sandbox lifecycle conditions and readiness.
repeated EndpointStatus endpoint_statuses = 10;
// Independent of infrastructure phase; retained across driver observations.
SandboxConfigurationAdmission configuration_admission = 11;
// Durable first-acceptance marker. Absent on legacy records; never reset by restart.
optional bool configuration_activated = 12;
// Gateway-owned provisioning deadlines, retained after timeout for inspection and retry.
SandboxProvisioning provisioning = 13;
// Consecutive policy-driven restart number in the current crash loop.
uint32 restart_count = 14;
// Deadline for the next restart attempt or recovery check.
google.protobuf.Timestamp next_restart_time = 15;
// Time when the current main process became ready.
google.protobuf.Timestamp main_process_started_time = 16;
}
// User-facing sandbox condition derived from platform or gateway observations.
message SandboxCondition {
reserved 5;
reserved "last_transition_time";
// Condition class, typically mirroring the underlying platform condition type.
string type = 1;
// Condition status value such as `True`, `False`, or `Unknown`.
string status = 2;
// Short machine-readable reason associated with the condition.
string reason = 3;
// Human-readable condition message.
string message = 4;
// Timestamp reported by the condition owner for the last transition.
google.protobuf.Timestamp transition_time = 105;
}
// High-level sandbox lifecycle phase derived by the gateway.
//
// Clients should rely on this normalized lifecycle summary for readiness and
// deletion decisions instead of interpreting raw conditions.
enum SandboxPhase {
SANDBOX_PHASE_UNSPECIFIED = 0;
SANDBOX_PHASE_PROVISIONING = 1;
SANDBOX_PHASE_READY = 2;
SANDBOX_PHASE_ERROR = 3;
SANDBOX_PHASE_DELETING = 4;
SANDBOX_PHASE_UNKNOWN = 5;
SANDBOX_PHASE_STOPPING = 6;
SANDBOX_PHASE_STOPPED = 7;
SANDBOX_PHASE_STARTING = 8;
// The canonical main process exited successfully and its result is final.
SANDBOX_PHASE_COMPLETED = 9;
reserved 10;
reserved "SANDBOX_PHASE_RESTARTING";
}
// Public platform event exposed on the sandbox watch stream.
message PlatformEvent {
reserved 1;
reserved "timestamp_ms";
// Time when the event occurred.
google.protobuf.Timestamp event_time = 101;
// Event source (e.g. "kubernetes", "docker", "process").
string source = 2;
// Event type/severity (e.g. "Normal", "Warning").
string type = 3;
// Short reason code (e.g. "Started", "Pulled", "Failed").
string reason = 4;
// Human-readable event message.
string message = 5;
// Optional metadata as key-value pairs.
map<string, string> metadata = 6;
}
// Create sandbox request.
message CreateSandboxRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 7;
SandboxSpec spec = 1;
// Optional user-supplied sandbox name. When empty the server generates one.
string name = 2;
// Optional labels for the sandbox (key-value metadata).
map<string, string> labels = 3;
// Optional annotations for the sandbox (non-selector metadata).
map<string, string> annotations = 4;
// One-shot launch hint indicating that the creating client will attach to
// the canonical main process. The supervisor keeps the terminal transport
// alive until that attachment connects and closes naturally.
bool await_main_process_attachment = 5;
// Workspace-scoped SandboxWorkloadTemplate name to resolve at creation time.
string workload_template = 6;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 8;
// HTTP services to expose when the sandbox is created. Endpoints are
// registered after the sandbox has been persisted and route only while the
// sandbox is ready.
repeated SandboxServiceExposure service_exposures = 9;
}
message CreateSandboxTemplateRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
SandboxWorkloadTemplate template = 1;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 3;
}
message GetSandboxTemplateRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
string name = 1;
}
message ListSandboxTemplatesRequest {
// Workspace scope. Named and all-workspaces selections are accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
// The maximum number of templates to return. Zero uses 100. Values above
// 1000 are coerced to 1000; negative values are invalid.
int32 page_size = 1;
// Token from a previous ListSandboxTemplates response. All other request
// parameters except page_size must match the request that produced it.
string page_token = 2;
// Optional label selector in key=value comma-separated form.
string label_selector = 3;
}
message DeleteSandboxTemplateRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
string name = 1;
// Succeed with ALREADY_ABSENT if the target is missing. Authorization and
// parent-workspace checks still apply.
bool allow_missing = 3;
// Optional nonzero UUID. Same ID and payload replay success for 24 hours.
string request_id = 4;
}
message SandboxTemplateResponse {
SandboxWorkloadTemplate template = 1;
}
message ListSandboxTemplatesResponse {
repeated SandboxWorkloadTemplate templates = 1;
// Token for the next page. Empty when there are no subsequent pages.
string next_page_token = 2;
}
message DeleteSandboxTemplateResponse {
reserved 1;
reserved "deleted";
DeletionOutcome outcome = 2;
}
// Request a gateway-owned staging slot for a local rootfs tar archive.
message BeginRootfsTarStagingRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
// Base file name of the local archive. The gateway uses it only to name the
// staged file; path separators and traversal components are rejected.
string file_name = 1;
// Size of the local archive in bytes, checked against the driver limit
// before the gateway allocates a slot.
uint64 size_bytes = 2;
}
// Gateway-issued staging slot.
message BeginRootfsTarStagingResponse {
reserved 4;
reserved "expires_at_ms";
// Opaque single-use token. Pass it as
// `template.driver_config.<driver>.rootfs_tar_staging_token` on
// CreateSandbox. The first CreateSandbox presenting it consumes it.
string staging_token = 1;
// Absolute path on the gateway host the client must write the archive to.
string upload_path = 2;
// Maximum accepted archive size in bytes, enforced again by the driver.
uint64 max_bytes = 3;
// Wall-clock deadline after which the gateway reclaims the slot.
google.protobuf.Timestamp expiration_time = 104;
}
// Get sandbox request.
message GetSandboxRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
string name = 1;
}
// List sandboxes request.
message ListSandboxesRequest {
// Workspace scope. Named and all-workspaces selections are accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
// The maximum number of sandboxes to return. Zero uses 100. Values above
// 1000 are coerced to 1000; negative values are invalid.
int32 page_size = 1;
// Token from a previous ListSandboxes response. All other request parameters
// except page_size must match the request that produced it.
string page_token = 2;
// Optional label selector for filtering (format: "key1=value1,key2=value2").
string label_selector = 3;
}
// List providers attached to a sandbox request.
message ListSandboxProvidersRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
string sandbox = 1;
// The maximum number of providers to return. Zero uses 100. Values above
// 1000 are coerced to 1000; negative values are invalid.
int32 page_size = 3;
// Token from a previous ListSandboxProviders response. All other request
// parameters except page_size must match the request that produced it.
string page_token = 4;
}
// Attach provider to sandbox request.
message AttachSandboxProviderRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
string sandbox = 1;
// Provider name to attach.
string provider = 2;
// Expected resource version for optimistic concurrency control.
// If 0, the server uses the current version (backward compatibility).
// If non-zero, the server validates that the sandbox's current resource_version
// matches this value before applying the mutation, returning ABORTED on mismatch.
uint64 expected_resource_version = 3;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 5;
}
// Detach provider from sandbox request.
message DetachSandboxProviderRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
string sandbox = 1;
// Provider name to detach.
string provider = 2;
// Expected resource version for optimistic concurrency control.
// If 0, the server uses the current version (backward compatibility).
// If non-zero, the server validates that the sandbox's current resource_version
// matches this value before applying the mutation, returning ABORTED on mismatch.
uint64 expected_resource_version = 3;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 5;
}
// Delete sandbox request.
message DeleteSandboxRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
// Canonical sandbox name.
string name = 1;
// Succeed with ALREADY_ABSENT if the target is missing. Does not wait for
// asynchronous cleanup and does not suppress authorization or parent errors.
bool allow_missing = 3;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 4;
}
// Stop sandbox request.
message StopSandboxRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
string name = 1;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 3;
}
// Start sandbox request.
message StartSandboxRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
string name = 1;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 3;
}
// Sandbox response.
message SandboxResponse {
Sandbox sandbox = 1;
// Service URLs created by CreateSandbox, keyed by service name. The empty
// key identifies the unnamed service. Other sandbox RPCs return an empty map.
map<string, string> service_urls = 2;
}
// List sandboxes response.
message ListSandboxesResponse {
repeated Sandbox sandboxes = 1;
// Token for the next page. Empty when there are no subsequent pages.
string next_page_token = 2;
}
// List providers attached to a sandbox response.
message ListSandboxProvidersResponse {
repeated openshell.datamodel.v1.Provider providers = 1;
// Token for the next page. Empty when there are no subsequent pages.
string next_page_token = 2;
}
// Attach provider to sandbox response.
message AttachSandboxProviderResponse {
Sandbox sandbox = 1;
// True when the provider was newly attached. False means it was already attached.
bool attached = 2;
// Persisted intent; readiness requires current supervisor observations.
ProviderMutationReceipt receipt = 3;
}
// Detach provider from sandbox response.
message DetachSandboxProviderResponse {
Sandbox sandbox = 1;
// True when the provider was removed. False means it was not attached.
bool detached = 2;
// Revocation is complete only when this receipt reports REVOKED.
ProviderMutationReceipt receipt = 3;
}
// Operation whose installed authority is tracked by a receipt.
enum ProviderMutationKind {
PROVIDER_MUTATION_KIND_UNSPECIFIED = 0;
PROVIDER_MUTATION_KIND_ATTACH = 1;
PROVIDER_MUTATION_KIND_DETACH = 2;
PROVIDER_MUTATION_KIND_UPDATE = 3;
// Reconstructed status for existing desired state without a mutation receipt.
PROVIDER_MUTATION_KIND_OBSERVE = 4;
}
// Readiness states describe persisted intent separately from installed state.
enum ProviderReadinessState {
PROVIDER_READINESS_STATE_UNSPECIFIED = 0;
PROVIDER_READINESS_STATE_PERSISTED = 1;
PROVIDER_READINESS_STATE_PENDING = 2;
PROVIDER_READINESS_STATE_READY = 3;
PROVIDER_READINESS_STATE_WITHHELD = 4;
PROVIDER_READINESS_STATE_REVOKED = 5;
PROVIDER_READINESS_STATE_FAILED = 6;
PROVIDER_READINESS_STATE_SUPERSEDED = 7;
}
// Closed reason categories are safe to display. Raw installation errors are
// never part of the readiness protocol.
enum ProviderReadinessReason {
PROVIDER_READINESS_REASON_UNSPECIFIED = 0;
PROVIDER_READINESS_REASON_WAITING_FOR_SUPERVISOR = 1;
PROVIDER_READINESS_REASON_WAITING_FOR_CREDENTIALS = 2;
PROVIDER_READINESS_REASON_WAITING_FOR_POLICY = 3;
PROVIDER_READINESS_REASON_WAITING_FOR_PROCESS = 4;
PROVIDER_READINESS_REASON_UNSUPPORTED_SUPERVISOR = 5;
PROVIDER_READINESS_REASON_CREDENTIALS_WITHHELD = 6;
PROVIDER_READINESS_REASON_CREDENTIAL_INSTALL_FAILED = 7;
PROVIDER_READINESS_REASON_POLICY_ACTIVATION_FAILED = 8;
PROVIDER_READINESS_REASON_PROCESS_INSTALL_FAILED = 9;
PROVIDER_READINESS_REASON_SUPERVISOR_DISCONNECTED = 10;
PROVIDER_READINESS_REASON_SUPERVISOR_LEASE_EXPIRED = 11;
PROVIDER_READINESS_REASON_DESIRED_STATE_CHANGED = 12;
PROVIDER_READINESS_REASON_CREDENTIAL_EXPIRED = 13;
PROVIDER_READINESS_REASON_LOCAL_POLICY = 14;
PROVIDER_READINESS_REASON_SNAPSHOT_MISMATCH = 15;
}
// Exact desired authority. Revisions are opaque identities, never ordered.
message ProviderDesiredIdentity {
string sandbox_id = 1;
string sandbox = 2;
string attachment_epoch = 3;
// Empty for a detached provider.
string provider_id = 4;
uint64 provider_resource_version = 5;
uint64 provider_env_revision = 6;
uint64 config_revision = 7;
string policy_hash = 8;
}
// Component whose desired state is tracked by a durable update operation.
enum ConfigComponent {
CONFIG_COMPONENT_UNSPECIFIED = 0;
CONFIG_COMPONENT_SANDBOX_CONFIG = 1;
CONFIG_COMPONENT_PROVIDER_ENVIRONMENT = 2;
}
// Identifies one component snapshot revision. Revisions are equality tokens,
// not members of one shared ordering domain.
message ConfigSnapshotRevision {
oneof component {
SandboxConfigRevision sandbox_config = 1;
uint64 provider_environment = 2;
// Complete desired authority for one sandbox-scoped provider mutation.
ProviderDesiredIdentity provider_target = 3;
}
}
// Identity needed to correlate effective sandbox configuration with the
// policy-history row whose apply status the gateway records.
message SandboxConfigRevision {
uint64 config_revision = 1;
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;
}
// Result of applying a component revision at its owning runtime boundary.
enum ConfigApplyOutcome {
CONFIG_APPLY_OUTCOME_UNSPECIFIED = 0;
CONFIG_APPLY_OUTCOME_APPLIED = 1;
CONFIG_APPLY_OUTCOME_IGNORED_DUPLICATE = 2;
CONFIG_APPLY_OUTCOME_IGNORED_STALE = 3;
CONFIG_APPLY_OUTCOME_RETAINED_LOCAL_OVERRIDE = 4;
CONFIG_APPLY_OUTCOME_DEGRADED = 5;
CONFIG_APPLY_OUTCOME_FAILED_RETAINED_LAST_KNOWN_GOOD = 6;
CONFIG_APPLY_OUTCOME_FAILED_CLOSED = 7;
CONFIG_APPLY_OUTCOME_UNSUPPORTED = 8;
}
// Durable lifecycle of one desired-state update operation.
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;
reserved 8, 9, 10;
reserved "created_at_ms", "updated_at_ms", "completed_at_ms";
google.protobuf.Timestamp created_time = 108;
google.protobuf.Timestamp updated_time = 109;
// Absent until the operation reaches a terminal state.
google.protobuf.Timestamp completed_time = 110;
}
// Immutable, secret-free record of one sandbox's intended provider mutation.
message ProviderMutationReceipt {
string receipt_id = 1;
// Shared by all sandbox receipts from one provider update.
string mutation_id = 2;
string provider = 3;
string workspace = 4;
ProviderMutationKind kind = 5;
ProviderDesiredIdentity desired = 6;
reserved 7;
reserved "persisted_at_ms";
google.protobuf.Timestamp persisted_time = 107;
}
// Installed state reported by the current supervisor. Process installation is
// acknowledged by the authenticated sandbox boundary after replacing its
// environment for future process launches.
message ProviderReadinessObservation {
// Gateway-issued identifier from the current ConnectSupervisor response.
string session_id = 1;
// Monotonic only within this connection; unrelated to revision fingerprints.
uint64 sequence = 2;
string attachment_epoch = 3;
uint64 provider_env_revision = 4;
uint64 config_revision = 5;
string policy_hash = 6;
bool credentials_installed = 7;
bool policy_active = 8;
bool launch_environment_installed = 9;
string process_instance_id = 10;
ProviderReadinessReason reason = 11;
}
// Operator view of desired and observed state; contains no credential material.
message ProviderReadinessStatus {
ProviderMutationReceipt receipt = 1;
ProviderReadinessState state = 2;
ProviderReadinessReason reason = 3;
ProviderReadinessObservation observed = 4;
string network_instance_id = 5;
reserved 6, 7;
reserved "observed_at_ms", "evaluated_at_ms";
// Absent until the current supervisor session supplies accepted evidence.
google.protobuf.Timestamp observed_time = 106;
google.protobuf.Timestamp evaluated_time = 107;
// Durable operation for the receipt, including its terminal apply outcome.
ConfigUpdateOperation operation = 8;
}
// Query an immutable receipt, or reconstruct the current desired state when
// receipt_id is empty. The sandbox identity must match the receipt.
message GetSandboxProviderStatusRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
string sandbox = 1;
string provider = 2;
string receipt_id = 3;
}
message GetSandboxProviderStatusResponse {
ProviderReadinessStatus status = 1;
}
// Installation evidence from the authenticated supervisor for this sandbox.
// A caller-provided instance identifier alone never establishes authority.
message ReportProviderReadinessRequest {
string sandbox_id = 1;
ProviderReadinessObservation observation = 2;
}
// Acknowledges accepted evidence without granting a separate session authority.
// Identical retries do not extend the evidence's original acceptance time.
message ReportProviderReadinessResponse {
uint64 accepted_sequence = 1;
reserved 2, 3;
reserved "report_interval_seconds", "observation_ttl_seconds";
google.protobuf.Duration report_interval = 102;
google.protobuf.Duration observation_ttl = 103;
}
// Delete sandbox response.
message DeleteSandboxResponse {
reserved 1;
reserved "deleted";
DeletionOutcome outcome = 2;
// Immutable identity of the targeted sandbox, empty for ALREADY_ABSENT.
// A same-name replacement is not part of this deletion.
string sandbox_id = 3;
}
// Create SSH session request.
message CreateSshSessionRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
string sandbox = 1;
}
// Create SSH session response.
//
// Fields are interpolated into an SSH `ProxyCommand` string that OpenSSH
// executes through `/bin/sh -c` on the caller's workstation. Servers MUST
// uphold the charset contract below; clients MUST reject responses that
// violate it. The client's own escaping provides defense-in-depth, but
// narrow charsets close injection vectors at the trust boundary.
message CreateSshSessionResponse {
reserved 8;
reserved "expires_at_ms";
// Sandbox id. [A-Za-z0-9._-]{1,128}.
string sandbox_id = 1;
// Session token for the gateway tunnel. URL-safe ASCII
// ([A-Za-z0-9._~+/=-]) up to 4096 bytes. No shell metacharacters or
// whitespace.
string token = 2 [(openshell.options.v1.secret) = true];
// Gateway host for SSH proxy connection. IPv4 address, bracketed IPv6
// address, or DNS hostname (Punycode-encoded for IDN). Alphanumeric plus
// `.-:[]` only, up to 253 bytes.
string gateway_host = 3;
// Gateway port for SSH proxy connection. Must be in range 1..=65535.
uint32 gateway_port = 4;
// Gateway scheme. Must be exactly "http" or "https".
string gateway_scheme = 5;
// Expected sandbox SSH host identity in OpenSSH SHA256:<base64> form.
// Stable for the sandbox's lifetime. Empty only for legacy runtimes.
string host_key_fingerprint = 7;
// Absolute expiry. Absence means no expiry.
google.protobuf.Timestamp expiration_time = 108;
}
// Request to expose an HTTP service running inside a sandbox.
message ExposeServiceRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5;
// Service name within the sandbox.
string name = 2;
// Loopback TCP port inside the sandbox.
uint32 target_port = 3;
// Whether to print/use the browser-facing service URL.
bool domain = 4;
string sandbox = 1;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 6;
// Application authorization behavior. Omission resolves to STRIP.
ServiceAuthorizationMode authorization_mode = 7;
}
// Request to fetch an exposed sandbox service endpoint.
message GetServiceRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
// Service name within the sandbox. Empty selects the unnamed endpoint.
string name = 2;
string sandbox = 1;
}
// Request to list exposed sandbox service endpoints.
message ListServicesRequest {
// Workspace scope. Named and all-workspaces selections are accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
// The maximum number of services to return. Zero uses 100. Values above
// 1000 are coerced to 1000; negative values are invalid.
int32 page_size = 2;
// Token from a previous ListServices response. All other request parameters
// except page_size must match the request that produced it.
string page_token = 3;
// Optional sandbox name. Empty lists endpoints for all sandboxes.
string sandbox = 1;
}
// Response containing exposed sandbox service endpoints.
message ListServicesResponse {
repeated ServiceEndpointResponse services = 1;
// Token for the next page. Empty when there are no subsequent pages.
string next_page_token = 2;
}
// Request to delete an exposed sandbox service endpoint.
message DeleteServiceRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
// Service name within the sandbox. Empty selects the unnamed endpoint.
string name = 2;
string sandbox = 1;
bool allow_missing = 4;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 5;
}
// Response for deleting an exposed sandbox service endpoint.
message DeleteServiceResponse {
reserved 1;
reserved "deleted";
DeletionOutcome outcome = 2;
}
// Persisted sandbox service endpoint.
message ServiceEndpoint {
// Kubernetes-style metadata.
openshell.datamodel.v1.ObjectMeta metadata = 1;
// Sandbox object ID.
string sandbox_id = 2;
// Sandbox name.
string sandbox = 3;
// Service name within the sandbox.
string name = 4;
// Loopback TCP port inside the sandbox.
uint32 target_port = 5;
// Whether browser-facing service routing is enabled for this endpoint.
bool domain = 6;
// Effective application authorization behavior for ingress requests.
ServiceAuthorizationMode authorization_mode = 7;
}
// Response containing a service endpoint and, when available, its local URL.
message ServiceEndpointResponse {
ServiceEndpoint endpoint = 1;
string url = 2;
}
// Revoke SSH session request.
message RevokeSshSessionRequest {
// Session token to revoke.
string token = 1 [(openshell.options.v1.secret) = true];
// A missing token is NOT_FOUND unless this is true. Revoking an existing,
// already-revoked session succeeds with COMPLETED.
bool allow_missing = 2;
}
// Revoke SSH session response.
message RevokeSshSessionResponse {
reserved 1;
reserved "revoked";
DeletionOutcome outcome = 2;
}
// Execute command request.
message ExecSandboxRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 12;
reserved 5;
reserved "timeout_seconds";
// Canonical sandbox name.
string sandbox = 1;
// Command and arguments.
repeated string command = 2;
// Optional working directory.
string workdir = 3;
// Optional environment overrides.
map<string, string> environment = 4;
// Optional execution timeout. Absence means no timeout.
google.protobuf.Duration execution_timeout = 105;
// Optional stdin payload passed to the command.
bytes stdin = 6;
// Request a pseudo-terminal for the remote command.
bool tty = 7;
// Initial terminal columns (used when tty=true, 0 = use default).
uint32 cols = 8;
// Initial terminal rows (used when tty=true, 0 = use default).
uint32 rows = 9;
// Skip sourcing shell login/profile startup files before running the command.
// When false (the default), the command runs through a login shell
// (`bash -lc`) so user startup files (.bash_profile/.profile, and .bashrc if
// sourced by them) are applied. When true, the command runs without those
// files (`bash -c`), for automation that needs predictable startup behavior.
bool no_login_shell = 10;
// Optional nonzero UUID for durable launch admission. Also applies to the
// initial ExecSandboxInteractive start message. Duplicates never relaunch,
// reattach, or replay output/stdin. Unconfirmed executions remain fenced;
// confirmed terminal executions retain the fence for 24 hours.
string request_id = 11;
}
// One stdout chunk from a sandbox exec.
message ExecSandboxStdout {
bytes data = 1;
}
// One stderr chunk from a sandbox exec.
message ExecSandboxStderr {
bytes data = 1;
}
// Final exit status for a sandbox exec.
message ExecSandboxExit {
int32 exit_code = 1;
}
// One event in a sandbox exec stream.
message ExecSandboxEvent {
oneof payload {
ExecSandboxStdout stdout = 1;
ExecSandboxStderr stderr = 2;
ExecSandboxExit exit = 3;
}
}
// Initial frame for one TCP forward stream.
message TcpForwardInit {
string sandbox = 1;
string workspace = 3;
// Optional service identifier for audit/correlation.
string service_id = 4;
// Target the gateway should request from the supervisor.
oneof target {
SshRelayTarget ssh = 5;
TcpRelayTarget tcp = 6;
}
// Optional target-specific authorization token. SSH targets use this as the
// short-lived SSH session token issued by CreateSshSession.
string authorization_token = 7 [(openshell.options.v1.secret) = true];
}
// A single frame on the CLI-to-gateway TCP forward stream.
message TcpForwardFrame {
oneof payload {
TcpForwardInit init = 1;
bytes data = 2;
}
}
// Client-to-server message for interactive exec.
message ExecSandboxInput {
oneof payload {
// First message: exec request metadata.
ExecSandboxRequest start = 1;
// Subsequent messages: raw stdin bytes.
bytes stdin = 2;
// Terminal window size change.
ExecSandboxWindowResize resize = 3;
}
}
// Terminal window resize event for interactive exec.
message ExecSandboxWindowResize {
uint32 cols = 1;
uint32 rows = 2;
}
// SSH session record stored in persistence.
message SshSession {
reserved 4;
reserved "expires_at_ms";
// Kubernetes-style metadata (id, name, labels, timestamps, resource version).
openshell.datamodel.v1.ObjectMeta metadata = 1;
// Sandbox id.
string sandbox_id = 2;
// Session token.
string token = 3 [(openshell.options.v1.secret) = true];
// Absolute expiry. Absence means no expiry.
google.protobuf.Timestamp expiration_time = 104;
// Revoked flag.
bool revoked = 5;
}
// Watch sandbox request.
message WatchSandboxRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 11;
reserved 8;
reserved "log_since_ms";
// Canonical sandbox name.
string sandbox = 1;
// Stream sandbox status snapshots.
bool follow_status = 2;
// Stream openshell-server process logs correlated to this sandbox.
bool follow_logs = 3;
// Stream platform events correlated to this sandbox.
bool follow_events = 4;
// Replay the last N log lines (best-effort) before following.
uint32 log_tail_lines = 5;
// Replay the last N platform events (best-effort) before following.
uint32 event_tail = 6;
// Stop streaming once the sandbox reaches READY or a terminal result phase
// (COMPLETED, STOPPED, or ERROR).
bool stop_on_terminal = 7;
// Only include log lines at or after this time. Absence means no time filter.
// Applies to both tail replay and live streaming.
google.protobuf.Timestamp since_time = 108;
// Filter by log source (e.g. "gateway", "sandbox"). Empty means all sources.
repeated string log_sources = 9;
// Minimum log level to include (e.g. "INFO", "WARN", "ERROR"). Empty means all levels.
string log_min_level = 10;
// Resume streaming after this cursor. Empty means no cursor resume: the
// server falls back to tail-limited replay controlled by log_tail_lines and
// event_tail. Otherwise set it to the highest `SandboxStreamEvent.cursor`
// already processed; the server replays only log and platform events after
// it, merged in cursor order, before resuming live delivery. If the requested
// cursor has already been trimmed from the server's buffer, the resume is
// unrecoverable and the stream terminates with OUT_OF_RANGE (see
// SandboxStreamWarning for the recoverable case).
//
// A cursor is bound to the cursor space that issued it. A gateway restart,
// teardown of the sandbox's buffers, or a reconnect to a different gateway
// replica starts a new space, and cursors from the previous one are rejected
// with OUT_OF_RANGE rather than silently suppressing live events beneath
// them. OUT_OF_RANGE is terminal for that cursor: restart the watch with an
// empty resume_after_cursor, because retrying the same token fails
// identically. A cursor this server could not have issued is rejected with
// INVALID_ARGUMENT.
string resume_after_cursor = 12;
}
// One event in a sandbox watch stream.
message SandboxStreamEvent {
oneof payload {
// Latest sandbox snapshot.
Sandbox sandbox = 1;
// One server log line/event.
SandboxLogLine log = 2;
// One platform event.
PlatformEvent event = 3;
// Recoverable warning from the server, e.g. messages dropped because a
// broadcast receiver lagged. The stream continues after this warning; the
// client can detect the gap from the warning itself.
SandboxStreamWarning warning = 4;
// Draft policy update notification.
DraftPolicyUpdate draft_policy_update = 5;
}
// Opaque position in this sandbox's cursor space, shared across the resumable
// log and platform event sources. Empty for non-resumable events (status
// snapshots, warnings).
//
// Do not parse this token; its encoding is not part of the contract. The only
// supported operation is comparing two non-empty cursors observed on the same
// stream and keeping the greater one, then passing it as
// WatchSandboxRequest.resume_after_cursor to resume without loss or
// duplication. That comparison is a plain byte-wise string comparison. It is
// well defined only within one stream: a stream never spans two cursor
// spaces, because a reset ends it.
string cursor = 6;
}
// Log line correlated to a sandbox.
message SandboxLogLine {
reserved 2;
reserved "timestamp_ms";
string sandbox_id = 1;
google.protobuf.Timestamp event_time = 102;
string level = 3;
string target = 4;
string message = 5;
// Log source: "gateway" (server-side) or "sandbox" (supervisor).
// Empty is treated as "gateway" for backward compatibility.
string source = 6;
// Structured key-value fields from the tracing event (e.g. dst_host, action).
map<string, string> fields = 7;
}
// Recoverable loss notification on a watch stream. Emitted when the server
// skips ahead after a broadcast lag instead of terminating; the stream keeps
// running. Cursors are opaque, so this message is the only signal that events
// were skipped. Unrecoverable loss (a trimmed or foreign resume cursor) is
// reported as an OUT_OF_RANGE stream status, not this message.
message SandboxStreamWarning {
string message = 1;
}
// Create provider request.
message CreateProviderRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
openshell.datamodel.v1.Provider provider = 1;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 3;
}
// Get provider request.
message GetProviderRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
string name = 1;
}
// List providers request.
message ListProvidersRequest {
// Workspace scope. Named and all-workspaces selections are accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
// The maximum number of providers to return. Zero uses 100. Values above
// 1000 are coerced to 1000; negative values are invalid.
int32 page_size = 1;
// Token from a previous ListProviders response. All other request parameters
// except page_size must match the request that produced it.
string page_token = 2;
}
// Update provider request.
message UpdateProviderRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
reserved 2;
reserved "credential_expires_at_ms";
openshell.datamodel.v1.Provider provider = 1;
// Optional per-credential expiry timestamps to merge into the provider.
// Omitted keys are unchanged. Use clear_credential_expiration_keys to remove
// an existing expiry.
map<string, google.protobuf.Timestamp> credential_expiration_times = 102;
// Credential keys whose existing expiry should be removed.
repeated string clear_credential_expiration_keys = 103;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 4;
}
// Delete provider request.
message DeleteProviderRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
string name = 1;
bool allow_missing = 4;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 5;
}
// Provider response.
message ProviderResponse {
openshell.datamodel.v1.Provider provider = 1;
// Selection-time sandbox target set for an update, with one receipt per target.
// Sandboxes attached later are outside this operation's readiness result.
repeated ProviderMutationReceipt target_receipts = 2;
// Identifies the update even when its target set is empty.
string mutation_id = 3;
}
// List providers response.
message ListProvidersResponse {
repeated openshell.datamodel.v1.Provider providers = 1;
// Token for the next page. Empty when there are no subsequent pages.
string next_page_token = 2;
}
// List provider type profiles request.
message ListProviderProfilesRequest {
// Workspace scope. Omit for platform profiles; otherwise select one named workspace.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
// The maximum number of profiles to return. Zero uses 100. Values above
// 1000 are coerced to 1000; negative values are invalid.
int32 page_size = 1;
// Token from a previous ListProviderProfiles response. All other request
// parameters except page_size must match the request that produced it.
string page_token = 2;
}
// Fetch provider type profile request.
message GetProviderProfileRequest {
// Workspace scope. Omit for platform profiles; otherwise select one named workspace.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
string id = 1;
}
// Provider profile payload with optional source metadata for diagnostics.
message ProviderProfileImportItem {
ProviderProfile profile = 1;
string source = 2;
}
// Provider profile validation diagnostic.
message ProviderProfileDiagnostic {
string source = 1;
string profile_id = 2;
string field = 3;
string message = 4;
string severity = 5;
}
// Endpoint selector for token grant audience overrides.
message ProviderCredentialTokenGrantAudienceOverride {
// Optional: endpoint host selector. If omitted, inherits the profile endpoint host.
string host = 1;
// Optional: endpoint port selector. If omitted, matches the expanded profile endpoint port.
uint32 port = 2;
// Optional: endpoint path selector. If omitted, inherits the profile endpoint path.
string path = 3;
// Resource audience to request for matching endpoints.
string audience = 4;
// Optional: OAuth2 scopes to request. If omitted, inherits the token grant scopes.
repeated string scopes = 5;
}
// Provider credential token grant configuration.
// When present, the credential is obtained dynamically via OAuth2 grant when needed.
enum ProviderCredentialTokenGrantType {
PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_UNSPECIFIED = 0;
PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_CLIENT_CREDENTIALS = 1;
PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_TOKEN_EXCHANGE = 2;
}
message ProviderCredentialTokenGrantSubjectToken {
// Source for the token exchange subject token. Phase one supports
// "provider_credential".
string source = 1;
// Provider credential key that stores the subject token.
string credential = 2;
// OAuth2 subject_token_type. If omitted, OpenShell uses
// urn:ietf:params:oauth:token-type:access_token.
string subject_token_type = 3;
}
message ProviderCredentialTokenGrant {
reserved 4;
reserved "cache_ttl_seconds";
// OAuth2 token endpoint URL (e.g., https://keycloak.example.com/realms/my-realm/protocol/openid-connect/token)
string token_endpoint = 1;
// Optional: default resource audience to request from the token service
string audience = 2;
// Optional: audience to request when fetching the JWT-SVID from SPIRE.
// If omitted, the sandbox derives this from token_endpoint.
string jwt_svid_audience = 6;
// Optional: OAuth2 scopes to request
repeated string scopes = 3;
// Optional token cache TTL override. If absent, use expires_in from the token response.
google.protobuf.Duration cache_ttl = 104;
// Optional: endpoint-specific resource audience overrides.
repeated ProviderCredentialTokenGrantAudienceOverride audience_overrides = 5;
// Optional: OAuth2 client_assertion_type value. If omitted, OpenShell uses
// urn:ietf:params:oauth:client-assertion-type:jwt-bearer.
string client_assertion_type = 7;
// Grant type. If omitted/unspecified, OpenShell treats this as client_credentials
// for backwards compatibility.
ProviderCredentialTokenGrantType grant_type = 8;
// Subject token metadata for token_exchange grants.
ProviderCredentialTokenGrantSubjectToken subject_token = 9;
// OAuth2 requested_token_type. If omitted for token_exchange, OpenShell uses
// urn:ietf:params:oauth:token-type:access_token.
string requested_token_type = 10;
}
// Provider credential declaration.
message ProviderProfileCredential {
string name = 1;
string description = 2;
repeated string env_vars = 3;
bool required = 4;
string auth_style = 5;
string header_name = 6;
string query_param = 7;
ProviderCredentialRefresh refresh = 8;
string path_template = 9;
ProviderCredentialTokenGrant token_grant = 10;
// Output-only gateway-derived endpoint authorities for resolved token grants.
// For inspected L7 requests, empty means no endpoint may obtain this grant.
// L4 injection uses selectors without checking these authorities.
// Profile-authored values are ignored; normal policies name the originating
// provider endpoint, while a global policy uses its own endpoint authorities.
repeated string token_grant_owners = 11;
}
enum ProviderCredentialRefreshStrategy {
PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED = 0;
PROVIDER_CREDENTIAL_REFRESH_STRATEGY_STATIC = 1;
PROVIDER_CREDENTIAL_REFRESH_STRATEGY_EXTERNAL = 2;
PROVIDER_CREDENTIAL_REFRESH_STRATEGY_OAUTH2_REFRESH_TOKEN = 3;
PROVIDER_CREDENTIAL_REFRESH_STRATEGY_OAUTH2_CLIENT_CREDENTIALS = 4;
PROVIDER_CREDENTIAL_REFRESH_STRATEGY_GOOGLE_SERVICE_ACCOUNT_JWT = 5;
PROVIDER_CREDENTIAL_REFRESH_STRATEGY_AWS_STS_ASSUME_ROLE = 6;
}
message ProviderCredentialRefreshMaterial {
string name = 1;
string description = 2;
bool required = 3;
bool secret = 4;
}
// Declares that a single refresh operation mints more than one credential.
// The refresh is attached to a primary credential; each additional output
// maps a strategy-defined semantic output id to a sibling credential whose
// env_vars receive the minted value.
message ProviderCredentialRefreshOutput {
string output = 1; // strategy-defined semantic output id (e.g. "session_token")
string credential = 2; // sibling credential name whose env_vars receive this output
}
message ProviderCredentialRefresh {
reserved 4, 5;
reserved "refresh_before_seconds", "max_lifetime_seconds";
ProviderCredentialRefreshStrategy strategy = 1;
string token_url = 2;
repeated string scopes = 3;
google.protobuf.Duration refresh_before = 104;
google.protobuf.Duration max_lifetime = 105;
repeated ProviderCredentialRefreshMaterial material = 6;
repeated ProviderCredentialRefreshOutput additional_outputs = 7;
}
message ProviderCredentialRefreshStatus {
reserved 6, 7, 8, 13;
reserved "expires_at_ms", "next_refresh_at_ms", "last_refresh_at_ms", "last_error_at_ms";
string provider = 1;
string provider_id = 2;
string credential_key = 3;
ProviderCredentialRefreshStrategy strategy = 4;
string status = 5;
google.protobuf.Timestamp expiration_time = 106;
// Next automatic refresh time. Absence means no automatic retry is scheduled;
// use recovery_action to determine the required recovery workflow.
google.protobuf.Timestamp next_refresh_time = 107;
google.protobuf.Timestamp last_refresh_time = 108;
string last_error = 9;
ProviderCredentialRefreshRecoveryAction recovery_action = 10;
// Stable gateway-owned failure identifier, for example
// "oauth_invalid_grant". This is not provider-controlled prose and
// incorporates any recognized top-level OAuth error classification.
string failure_code = 11;
// A bounded, recognized provider subtype that refines failure_code; clients
// do not need a separate provider_error field. Unknown provider-controlled
// values are not persisted or returned.
string provider_error_subtype = 12;
google.protobuf.Timestamp last_error_time = 113;
}
// Provider profile local discovery declaration.
message ProviderProfileDiscovery {
// Credential names from ProviderProfile.credentials eligible for local discovery.
repeated string credentials = 1;
}
message GetProviderRefreshStatusRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
string provider = 1;
string credential_key = 2;
}
message GetProviderRefreshStatusResponse {
repeated ProviderCredentialRefreshStatus credentials = 1;
}
message ConfigureProviderRefreshRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 7;
reserved 6;
reserved "expires_at_ms";
string provider = 1;
string credential_key = 2;
ProviderCredentialRefreshStrategy strategy = 3;
map<string, string> material = 4 [(openshell.options.v1.secret) = true];
// Additional material names the caller requests be stored as secrets. Every
// name must be present in material. The server also classifies secrets from
// the authoritative provider profile and refresh strategy.
repeated string secret_material_keys = 5;
google.protobuf.Timestamp expiration_time = 106;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 8;
}
message ConfigureProviderRefreshResponse {
ProviderCredentialRefreshStatus status = 1;
}
message RotateProviderCredentialRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
string provider = 1;
string credential_key = 2;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 4;
}
message RotateProviderCredentialResponse {
ProviderCredentialRefreshStatus status = 1;
}
message DeleteProviderRefreshRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
string provider = 1;
string credential_key = 2;
bool allow_missing = 5;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 6;
}
message DeleteProviderRefreshResponse {
reserved 1;
reserved "deleted";
DeletionOutcome outcome = 2;
}
// Stable provider profile categories used by clients for grouping and filtering.
enum ProviderProfileCategory {
PROVIDER_PROFILE_CATEGORY_UNSPECIFIED = 0;
PROVIDER_PROFILE_CATEGORY_OTHER = 1;
PROVIDER_PROFILE_CATEGORY_INFERENCE = 2;
PROVIDER_PROFILE_CATEGORY_AGENT = 3;
PROVIDER_PROFILE_CATEGORY_SOURCE_CONTROL = 4;
PROVIDER_PROFILE_CATEGORY_MESSAGING = 5;
PROVIDER_PROFILE_CATEGORY_DATA = 6;
PROVIDER_PROFILE_CATEGORY_KNOWLEDGE = 7;
}
// Provider type profile metadata exposed to clients.
message ProviderProfile {
string id = 1;
string display_name = 2;
string description = 3;
ProviderProfileCategory category = 4;
repeated ProviderProfileCredential credentials = 5;
repeated openshell.sandbox.v1.NetworkEndpoint endpoints = 6;
repeated openshell.sandbox.v1.NetworkBinary binaries = 7;
bool inference_capable = 8;
ProviderProfileDiscovery discovery = 9;
// Storage resource version for custom profiles. Built-in profiles and new
// profile files use 0. Gateway responses set this for stored custom profiles.
// Update calls use this for optimistic concurrency.
uint64 resource_version = 10;
// Optional non-secret annotations attached by profile sources or importers.
map<string, string> annotations = 11;
// Server-set provenance: "builtin", "user", or "interceptor/{name}".
// Ignored on import/update payloads.
string source = 12;
// Server-set visibility: "platform", "workspace", or empty for
// non-scoped sources. Ignored on import/update payloads.
string scope = 13;
// EXPERIMENTAL: Non-secret files rendered from provider configuration for the
// workload. This API and its behavior may change or be removed.
repeated ProviderProfileFile files = 14;
}
// EXPERIMENTAL: Provider file templates may change or be removed.
message ProviderProfileFile {
// One virtual file name below /run/openshell/providers/<provider>/.
string path = 1;
// UTF-8 template. Only {{config.KEY}} references are supported.
string content = 2;
// Optional environment variable containing the virtual absolute path.
string env_var = 3;
}
// Provider profile response.
message ProviderProfileResponse {
ProviderProfile profile = 1;
}
// List provider profiles response.
message ListProviderProfilesResponse {
repeated ProviderProfile profiles = 1;
// Token for the next page. Empty when there are no subsequent pages.
string next_page_token = 2;
}
// Import custom provider profiles request.
message ImportProviderProfilesRequest {
// Workspace scope. Omit for platform profiles; otherwise select one named workspace.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
repeated ProviderProfileImportItem profiles = 1;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 3;
}
// Import custom provider profiles response.
message ImportProviderProfilesResponse {
repeated ProviderProfileDiagnostic diagnostics = 1;
repeated ProviderProfile profiles = 2;
bool imported = 3;
}
// Update one custom provider profile request.
message UpdateProviderProfilesRequest {
// Workspace scope. Omit for platform profiles; otherwise select one named workspace.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
ProviderProfileImportItem profile = 1;
// Expected storage resource version for optimistic concurrency control.
// If 0, the server uses the resource_version embedded in profile.profile.
// Updates without a non-zero version are rejected to prevent stale files from
// silently overwriting newer profile definitions.
uint64 expected_resource_version = 2;
// Existing custom provider profile ID to update. The payload ID must match.
string id = 3;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 5;
}
// Update one custom provider profile response.
message UpdateProviderProfilesResponse {
repeated ProviderProfileDiagnostic diagnostics = 1;
ProviderProfile profile = 2;
bool updated = 3;
}
// Lint provider profiles request.
message LintProviderProfilesRequest {
// Workspace scope. Omit for platform profiles; otherwise select one named workspace.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
repeated ProviderProfileImportItem profiles = 1;
}
// Lint provider profiles response.
message LintProviderProfilesResponse {
repeated ProviderProfileDiagnostic diagnostics = 1;
bool valid = 2;
}
// Delete provider response.
message DeleteProviderResponse {
reserved 1;
reserved "deleted";
DeletionOutcome outcome = 2;
}
// Delete custom provider profile request.
message DeleteProviderProfileRequest {
// Workspace scope. Omit for platform profiles; otherwise select one named workspace.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
string id = 1;
bool allow_missing = 3;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 4;
}
// Delete custom provider profile response.
message DeleteProviderProfileResponse {
reserved 1;
reserved "deleted";
DeletionOutcome outcome = 2;
}
// Get sandbox provider environment request.
message GetSandboxProviderEnvironmentRequest {
// The sandbox ID.
string sandbox_id = 1;
// Whether the requesting supervisor enforces endpoint bindings for static
// provider credentials. Gateways withhold static credential material when
// this capability is absent.
bool supports_static_credential_bindings = 2;
}
// One network endpoint at which a static provider credential may be resolved.
message StaticCredentialEndpointBinding {
string host = 1;
uint32 port = 2;
string path = 3;
}
// Endpoint allowlist for one static provider credential environment variable.
message StaticCredentialBinding {
repeated StaticCredentialEndpointBinding endpoints = 1;
// Stable identity of the provider credential that produced this binding.
// Supervisors use it to retain old revision placeholders only across
// rotations of the same provider credential.
string credential_identity = 2;
// Opaque gateway-issued handle for a refresh-managed credential identity
// epoch. When non-empty, supervisors keep the workload placeholder stable
// across access-token rotations and replace only the resolver value. The
// handle changes when the sandbox, provider, credential key, refresh
// authorization epoch, or endpoint authorization boundary changes.
string workload_credential_handle = 3;
}
// Get sandbox provider environment response.
message GetSandboxProviderEnvironmentResponse {
reserved 3;
reserved "credential_expires_at_ms";
// Provider credential environment variables.
map<string, string> environment = 1 [(openshell.options.v1.secret) = true];
// Fingerprint for the provider credential inputs that produced environment.
uint64 provider_env_revision = 2;
// Expiration timestamps for returned environment variables.
map<string, google.protobuf.Timestamp> credential_expiration_times = 103;
// Dynamic credentials that require token grants or other runtime injection.
// Maps endpoint-bound provider metadata to credential metadata.
// Supervisor uses this to inject Authorization headers for token grant credentials.
map<string, ProviderProfileCredential> dynamic_credentials = 4;
// Endpoint allowlists for static credential environment variables. Metadata
// can be incomplete when a provider profile is invalid; capable supervisors
// reject all static material in that snapshot while preserving independently
// endpoint-bound dynamic credentials.
map<string, StaticCredentialBinding> static_credential_bindings = 5;
// Environment variables that contain provider configuration rather than
// credentials and therefore do not require endpoint-scoped resolution.
repeated string non_secret_environment_keys = 6;
// Attachment identity captured with the returned provider records.
string provider_attachment_epoch = 7;
// Effective policy identity used to derive this snapshot's endpoint bindings.
string policy_hash = 8;
// Nonzero when material was withheld; installing an empty map is not readiness.
ProviderReadinessReason readiness_reason = 9;
// Complete desired set of non-secret managed files, keyed by absolute path.
map<string, string> files = 10;
}
message ExchangeProviderSubjectTokenRequest {
// The sandbox ID. Must match the authenticated sandbox principal.
string sandbox_id = 1;
// Attached provider record holding the configured subject token credential.
string provider = 2;
// Provider profile credential that declares the token_exchange grant.
string credential_key = 3;
// Supervisor JWT-SVID. The gateway verifies this and uses its `sub` claim
// as the requested audience for the intermediate token.
string supervisor_jwt_svid = 4 [(openshell.options.v1.secret) = true];
}
message ExchangeProviderSubjectTokenResponse {
reserved 2;
reserved "expires_in";
string access_token = 1 [(openshell.options.v1.secret) = true];
google.protobuf.Duration expires_after = 102;
string token_type = 3;
}
// ---------------------------------------------------------------------------
// Policy update messages
// ---------------------------------------------------------------------------
// Update sandbox policy request.
message UpdateConfigRequest {
// Workspace scope. Omit for global updates; otherwise select one named workspace.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 10;
// The new policy to apply.
//
// Sandbox scope (`global=false`):
// - only network_policies may differ from create-time
// policy; static fields must match version 1.
//
// Global scope (`global=true`):
// - applies to all sandboxes in full (no merge).
openshell.sandbox.v1.SandboxPolicy policy = 2;
// Optional single setting key to mutate.
string setting_key = 3;
// Setting value for upsert operations.
openshell.sandbox.v1.SettingValue setting_value = 4;
// Delete the setting key from scope.
// Sandbox-scoped deletes are rejected; only global delete is supported.
bool delete_setting = 5;
// Apply mutation at gateway-global scope.
bool global = 6;
// Batched incremental policy merge operations. Sandbox-scoped only.
repeated PolicyMergeOperation merge_operations = 7;
// Expected resource version for optimistic concurrency control (sandbox-scoped only).
// If 0, the server uses the current version (backward compatibility).
// If non-zero, the server validates that the sandbox's current resource_version
// matches this value before applying the mutation, returning ABORTED on mismatch.
// Ignored for global-scoped updates.
uint64 expected_resource_version = 8;
// Caller-provided annotations associated with a sandbox-scoped update. Values
// must not contain secrets; the gateway treats them as opaque metadata and does
// not interpret or verify their semantics. For policy updates, the gateway
// stores the annotations immutably with the revision and merges them into
// sandbox metadata as a convenience projection. For setting-only updates, it
// only merges them into sandbox metadata.
map<string, string> annotations = 9;
// Required for sandbox-scoped updates and empty for global updates.
string sandbox = 1;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 11;
}
message PolicyMergeOperation {
oneof operation {
AddNetworkRule add_rule = 1;
RemoveNetworkEndpoint remove_endpoint = 2;
RemoveNetworkRule remove_rule = 3;
AddDenyRules add_deny_rules = 4;
AddAllowRules add_allow_rules = 5;
RemoveNetworkBinary remove_binary = 6;
}
}
message AddNetworkRule {
string rule_name = 1;
openshell.sandbox.v1.NetworkPolicyRule rule = 2;
}
message RemoveNetworkEndpoint {
string rule_name = 1;
string host = 2;
uint32 port = 3;
}
message RemoveNetworkRule {
string rule_name = 1;
}
// Exact endpoint and complete authorization scope affected by an L7 append.
// All ports and binaries must match the stored target; omitted scope is invalid.
message L7RuleTarget {
string rule_name = 1;
string host = 2;
repeated uint32 ports = 3;
// An absent path requires a unique endpoint. An empty path selects an
// endpoint without a path selector. This is not the appended request path.
optional string path = 4;
// Declare either a nonempty binary list or any_binary, never both.
repeated openshell.sandbox.v1.NetworkBinary binaries = 5;
bool any_binary = 6;
}
message AddDenyRules {
reserved 1, 2;
reserved "host", "port";
repeated openshell.sandbox.v1.L7DenyRule deny_rules = 3;
L7RuleTarget target = 4;
}
message AddAllowRules {
reserved 1, 2;
reserved "host", "port";
repeated openshell.sandbox.v1.L7Rule rules = 3;
L7RuleTarget target = 4;
}
message RemoveNetworkBinary {
string rule_name = 1;
string binary_path = 2;
}
// Update sandbox policy response.
message UpdateConfigResponse {
// Assigned policy version (monotonically increasing per sandbox).
uint32 version = 1;
// SHA-256 hash of the serialized policy payload.
string policy_hash = 2;
// Settings revision for the scope that was modified.
uint64 settings_revision = 3;
// True when a setting delete operation removed an existing key.
bool deleted = 4;
// Sandbox metadata annotations after the update. Empty for global updates.
map<string, string> annotations = 5;
}
// Get sandbox policy status request.
message GetSandboxPolicyStatusRequest {
// Workspace scope. Omit for global queries; otherwise select one named workspace.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
// The specific policy version to query. 0 means latest.
uint32 version = 2;
// Query global policy revisions instead of a sandbox-scoped one.
bool global = 3;
string sandbox = 1;
}
// Get sandbox policy status response.
message GetSandboxPolicyStatusResponse {
// The queried policy revision.
SandboxPolicyRevision revision = 1;
// The currently active (loaded) policy version for this sandbox.
uint32 active_version = 2;
}
// List sandbox policies request.
message ListSandboxPoliciesRequest {
// Workspace scope. Omit for global queries; otherwise select one named workspace.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5;
// The maximum number of revisions to return. Zero uses 100. Values above
// 1000 are coerced to 1000; negative values are invalid.
int32 page_size = 2;
// Token from a previous ListSandboxPolicies response. All other request
// parameters except page_size must match the request that produced it.
string page_token = 3;
// List global policy revisions instead of sandbox-scoped ones.
bool global = 4;
string sandbox = 1;
}
// List sandbox policies response.
message ListSandboxPoliciesResponse {
// Invalid historical payloads remain visible as failed projections so one
// legacy row cannot hide the rest of the policy history.
repeated SandboxPolicyRevision revisions = 1;
// Token for the next page. Empty when there are no subsequent pages.
string next_page_token = 2;
}
// Report policy load status (called by sandbox runtime after reload attempt).
message ReportPolicyStatusRequest {
// Sandbox id.
string sandbox_id = 1;
// The policy version that was attempted.
uint32 version = 2;
// Load result status.
PolicyStatus status = 3;
// Error message if status is FAILED.
string load_error = 4;
}
// Report policy status response.
message ReportPolicyStatusResponse {}
enum ConfigurationAdmissionState {
CONFIGURATION_ADMISSION_STATE_UNSPECIFIED = 0;
CONFIGURATION_ADMISSION_STATE_PENDING = 1;
CONFIGURATION_ADMISSION_STATE_ACCEPTED = 2;
CONFIGURATION_ADMISSION_STATE_REJECTED = 3;
}
message SandboxConfigurationAdmission {
string instance_id = 1;
ConfigurationAdmissionState state = 2;
uint32 policy_version = 3;
string policy_hash = 4;
uint64 config_revision = 5;
uint64 provider_env_revision = 6;
string error = 7;
}
message ReportSandboxConfigurationRequest {
string sandbox_id = 1;
SandboxConfigurationAdmission admission = 2;
// Pending registration replaces only this previously observed instance.
string expected_instance_id = 3;
}
message ReportSandboxConfigurationResponse {}
// A versioned policy revision with metadata.
message SandboxPolicyRevision {
reserved 5, 6;
reserved "created_at_ms", "loaded_at_ms";
// Policy version (monotonically increasing per sandbox).
uint32 version = 1;
// SHA-256 hash of the canonical serialized policy payload. Empty in a
// ListSandboxPolicies projection when the stored payload is invalid under
// the current schema and therefore has no trusted canonical identity.
string policy_hash = 2;
// Load status of this revision. ListSandboxPolicies reports FAILED when a
// stored historical payload is invalid under the current schema, regardless
// of its persisted sandbox load status.
PolicyStatus status = 3;
// Sandbox load error, or the schema-validation diagnostic for an invalid
// historical row returned by ListSandboxPolicies.
string load_error = 4;
// Time when this revision was created.
google.protobuf.Timestamp created_time = 105;
// Time when this revision was loaded by the sandbox. Absent if not loaded.
google.protobuf.Timestamp loaded_time = 106;
// The full policy (only populated when explicitly requested).
openshell.sandbox.v1.SandboxPolicy policy = 7;
// Immutable provenance supplied with this policy revision.
map<string, string> provenance = 8;
}
// Policy load status.
enum PolicyStatus {
POLICY_STATUS_UNSPECIFIED = 0;
// Server received the update; sandbox has not yet loaded it.
POLICY_STATUS_PENDING = 1;
// Sandbox successfully applied this policy version.
POLICY_STATUS_LOADED = 2;
// Sandbox attempted to apply but failed; LKG policy remains active.
// ListSandboxPolicies also uses FAILED for historical payloads that are
// invalid under the current schema; load_error contains the diagnostic.
POLICY_STATUS_FAILED = 3;
// A newer version was persisted before the sandbox loaded this one.
POLICY_STATUS_SUPERSEDED = 4;
}
// ---------------------------------------------------------------------------
// Sandbox logs messages
// ---------------------------------------------------------------------------
// Get sandbox logs request (one-shot fetch).
message GetSandboxLogsRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 6;
reserved 3;
reserved "since_ms";
// Canonical sandbox name.
string sandbox = 1;
// Maximum number of log lines to return. 0 means use default (2000).
uint32 lines = 2;
// Only include logs at or after this time. Absence means no filter.
google.protobuf.Timestamp since_time = 103;
// Filter by log source (e.g. "gateway", "sandbox"). Empty means all sources.
repeated string sources = 4;
// Minimum log level to include (e.g. "INFO", "WARN", "ERROR"). Empty means all levels.
string min_level = 5;
}
// Batch of log lines pushed from sandbox to server.
message PushSandboxLogsRequest {
// The sandbox ID.
string sandbox_id = 1;
// Log lines to ingest.
repeated SandboxLogLine logs = 2;
}
// Push sandbox logs response.
message PushSandboxLogsResponse {}
// Get sandbox logs response.
message GetSandboxLogsResponse {
// Log lines in chronological order.
repeated SandboxLogLine logs = 1;
// Total number of lines in the server's buffer for this sandbox.
uint32 buffer_total = 2;
}
// ---------------------------------------------------------------------------
// Supervisor session messages
// ---------------------------------------------------------------------------
// Envelope for supervisor-to-gateway messages on the ConnectSupervisor stream.
message SupervisorMessage {
oneof payload {
SupervisorHello hello = 1;
SupervisorHeartbeat heartbeat = 2;
RelayOpenResult relay_open_result = 3;
RelayClose relay_close = 4;
}
}
// Envelope for gateway-to-supervisor messages on the ConnectSupervisor stream.
message GatewayMessage {
oneof payload {
SessionAccepted session_accepted = 1;
SessionRejected session_rejected = 2;
GatewayHeartbeat heartbeat = 3;
RelayOpen relay_open = 4;
RelayClose relay_close = 5;
SessionRedirect session_redirect = 6;
}
}
// Supervisor identifies itself and the sandbox it manages.
message SupervisorHello {
// Sandbox ID this supervisor manages.
string sandbox_id = 1;
// Supervisor instance ID (e.g. boot id or process epoch).
string instance_id = 2;
// Monotonic counter scoped to instance_id. Incremented for each reconnect so
// gateways can distinguish a fresh supervisor connection from stale cleanup.
uint64 connection_epoch = 3;
// The supervisor can report credential, policy, and launch-environment installation.
bool supports_provider_readiness = 4;
// Set when this connection is the result of following a SessionRedirect. The
// receiving gateway serves a redirected hello locally instead of redirecting
// again, so a disagreement about membership cannot bounce the supervisor
// between replicas. A gateway that is shutting down may still redirect it.
bool redirected = 5;
// The supervisor understands SessionRedirect. Gateways never redirect a
// supervisor that does not set this.
bool supports_session_redirect = 6;
}
// Gateway accepts the supervisor session.
message SessionAccepted {
reserved 2;
reserved "heartbeat_interval_secs";
// Gateway-assigned session ID for this connection.
string session_id = 1;
// Recommended heartbeat interval.
google.protobuf.Duration heartbeat_interval = 102;
}
// Gateway rejects the supervisor session.
message SessionRejected {
// Human-readable rejection reason.
string reason = 1;
}
// Gateway declines to own this session and names the replica that should.
//
// Sent instead of SessionAccepted when the placement ring puts the sandbox on
// a different live replica, and to established sessions when their gateway
// shuts down. The supervisor reconnects to peer_endpoint once with
// SupervisorHello.redirected set, and falls back to its configured gateway
// address if that fails.
message SessionRedirect {
// Peer endpoint of the replica that should own this sandbox.
string peer_endpoint = 1;
// Replica ID that should own this sandbox, for logging.
string owner_replica_id = 2;
}
// Supervisor heartbeat.
message SupervisorHeartbeat {}
// Gateway heartbeat.
message GatewayHeartbeat {}
// Terminal result reported before the supervisor shuts down. A successful RPC
// response confirms that the result was durably handled by the gateway.
message ReportMainProcessExitRequest {
string sandbox_id = 1;
string instance_id = 2;
// Normalized process result. Signal exits use 128 + signal number.
int32 exit_code = 3;
}
message ReportMainProcessExitResponse {}
// Terminal-delivery completion reported after all expected foreground SSH
// attachments close. A successful response permits ephemeral cleanup.
message FinalizeMainProcessExitRequest {
string sandbox_id = 1;
string instance_id = 2;
}
message FinalizeMainProcessExitResponse {}
// Gateway requests the supervisor to open a relay channel.
//
// On receiving this, the supervisor should initiate a RelayStream RPC to
// the gateway, sending a RelayInit in the first RelayFrame to associate
// the new HTTP/2 stream with the pending relay slot. The supervisor
// bridges that stream to the requested local target.
message RelayOpen {
// Gateway-allocated channel identifier (UUID).
string channel_id = 1;
// Target the supervisor should dial inside the sandbox.
// If absent, supervisors treat the relay as SSH for compatibility.
oneof target {
SshRelayTarget ssh = 2;
TcpRelayTarget tcp = 3;
}
// Optional service identifier for audit/correlation.
string service_id = 5;
}
// Built-in SSH relay target.
message SshRelayTarget {}
// TCP target dialed by the supervisor from inside the sandbox.
message TcpRelayTarget {
// Phase 1 accepts loopback only: 127.0.0.1, ::1, or localhost.
string host = 1;
// Target port. Must fit in u16 and be non-zero.
uint32 port = 2;
}
// Initial RelayStream frame sent by the supervisor to claim a pending relay.
message RelayInit {
// Gateway-allocated channel identifier (UUID).
string channel_id = 1;
}
// A single frame on the RelayStream RPC.
//
// The supervisor MUST send `init` as the first frame. All subsequent frames
// in either direction carry raw bytes in `data`.
message RelayFrame {
oneof payload {
RelayInit init = 1;
bytes data = 2;
}
}
// Initial frame for gateway peer relay forwarding.
message PeerRelayInit {
// Stable sandbox UUID whose supervisor relay should be opened.
string sandbox_id = 1;
// Relay target to ask the owning gateway to open on its local supervisor
// session. The channel_id is assigned by the forwarding gateway.
RelayOpen relay_open = 2;
// Gateway replica id that initiated the peer relay.
string requester_replica_id = 3;
}
// A single frame on the gateway-to-gateway peer relay RPC.
message PeerRelayFrame {
oneof payload {
PeerRelayInit init = 1;
bytes data = 2;
}
}
// Supervisor reports the result of a relay open request.
message RelayOpenResult {
// Channel identifier from the RelayOpen request.
string channel_id = 1;
// True if the relay was successfully established.
bool success = 2;
// Error message if success is false.
string error = 3;
}
// Either side requests closure of a relay channel.
message RelayClose {
// Channel identifier to close.
string channel_id = 1;
// Optional reason for closure.
string reason = 2;
}
// ---------------------------------------------------------------------------
// Service status
// ---------------------------------------------------------------------------
// Service status enum.
enum ServiceStatus {
SERVICE_STATUS_UNSPECIFIED = 0;
SERVICE_STATUS_HEALTHY = 1;
SERVICE_STATUS_DEGRADED = 2;
SERVICE_STATUS_UNHEALTHY = 3;
}
// ---------------------------------------------------------------------------
// Draft policy recommendation messages
// ---------------------------------------------------------------------------
// Observed HTTP method+path pattern from L7 inspection.
message L7RequestSample {
// HTTP method: GET, POST, PUT, DELETE, etc.
string method = 1;
// HTTP path: /v1/models, /repos/myorg/issues
string path = 2;
// L7 decision: "audit" or "deny" (allowed requests not collected).
string decision = 3;
// Number of times this (method, path) was observed.
uint32 count = 4;
}
// Structured denial summary from sandbox aggregator.
message DenialSummary {
reserved 7, 8;
reserved "first_seen_ms", "last_seen_ms";
// Sandbox ID that produced this summary.
string sandbox_id = 1;
// Denied destination host.
string host = 2;
// Denied destination port.
uint32 port = 3;
// Binary that attempted the connection.
string binary = 4;
// Process ancestor chain.
repeated string ancestors = 5;
// Denial reason from OPA evaluation.
string deny_reason = 6;
// Time of the first denial.
google.protobuf.Timestamp first_seen_time = 107;
// Time of the most recent denial.
google.protobuf.Timestamp last_seen_time = 108;
// Number of denials in the current window.
uint32 count = 9;
// Events dropped during aggregator cooldown.
uint32 suppressed_count = 10;
// Cumulative lifetime count (never resets).
uint32 total_count = 11;
// Distinct cmdline strings observed (sanitized of credentials).
repeated string sample_cmdlines = 12;
// SHA-256 of the binary for audit trail.
string binary_sha256 = 13;
// True if emitted by stale-flush rather than threshold.
bool persistent = 14;
// Denial category: "l4_deny", "l7_deny", "l7_audit", "ssrf".
string denial_stage = 15;
// Observed HTTP request patterns (from L7 inspection).
repeated L7RequestSample l7_request_samples = 16;
// True if L7 inspection was active during observation window.
bool l7_inspection_active = 17;
}
// Count of denied actions grouped only by sanitized telemetry category.
message DenialGroupCount {
// Sanitized denial category, e.g. "connect_policy", "l7_policy", "ssrf".
string deny_group = 1;
// Number of denied actions in this category.
uint32 denied_count = 2;
}
// Anonymous sandbox network activity counters. This intentionally excludes
// hosts, paths, binaries, raw deny reasons, sandbox IDs, and user content.
message NetworkActivitySummary {
// Total observed network activities in the current window.
uint32 network_activity_count = 1;
// Total denied actions in the current window.
uint32 denied_action_count = 2;
// Denied action counts grouped by sanitized category.
repeated DenialGroupCount denials_by_group = 3;
}
// A proposed policy rule with rationale and approval status.
message PolicyChunk {
reserved 9, 10, 14, 15;
reserved "created_at_ms", "decided_at_ms", "first_seen_ms", "last_seen_ms";
// Unique chunk identifier.
string id = 1;
// Approval status: "pending", "approved", "rejected".
string status = 2;
// Proposed network_policies map key.
string rule_name = 3;
// The proposed network policy rule.
openshell.sandbox.v1.NetworkPolicyRule proposed_rule = 4;
// Human-readable explanation of why this rule is proposed.
string rationale = 5;
// Security concerns flagged by analysis (empty if none).
string security_notes = 6;
// Analysis confidence (0.0-1.0). 0 for mechanistic mode.
float confidence = 7;
// IDs of denial summaries that led to this chunk.
repeated string denial_summary_ids = 8;
// Time when this chunk was created.
google.protobuf.Timestamp created_time = 109;
// Time when the user approved or rejected the chunk. Absent if undecided.
google.protobuf.Timestamp decided_time = 110;
// Recommendation stage: "initial" or "refined" (progressive L7 visibility).
string stage = 11;
// For stage="refined": the initial chunk this replaces.
string supersedes_chunk_id = 12;
// How many times this endpoint has been seen across denial flush cycles.
int32 hit_count = 13;
// First time this endpoint was proposed.
google.protobuf.Timestamp first_seen_time = 114;
// Most recent time this endpoint was proposed again.
google.protobuf.Timestamp last_seen_time = 115;
// Binary path that triggered the denial (denormalized for display convenience).
string binary = 16;
// Validation verdict from gateway-side static checks (prover output).
// Free-form summary string for human consumption in the inbox card.
// Empty until the prover has run for this chunk.
string validation_result = 17;
// Operator-supplied free-form text accompanying a rejection. Populated
// when the reviewer rejects via `RejectDraftChunkRequest.reason`; surfaced
// back to the in-sandbox agent so it can revise the proposal.
// Empty for non-rejected chunks.
string rejection_reason = 18;
// Gateway-side merge/application preflight failure. Kept separate from
// prover output and operator rejection so clients can explain why a
// prover-clean proposal is not currently applicable.
string application_error = 19;
// Opaque digest binding review to the exact live inputs and complete
// effective candidate evaluated by the gateway.
string review_token = 20;
// Deterministic hashes for compact candidate display and diagnostics.
string current_effective_policy_hash = 21;
string candidate_effective_policy_hash = 22;
// Complete effective policies used for review. These contain policy
// configuration only; credential secret values are never materialized.
openshell.sandbox.v1.SandboxPolicy current_effective_policy = 23;
openshell.sandbox.v1.SandboxPolicy candidate_effective_policy = 24;
}
// Notification that the draft policy was updated.
message DraftPolicyUpdate {
// Current draft version.
uint64 draft_version = 1;
// Number of new chunks added in this update.
uint32 new_chunks = 2;
// Total pending chunks awaiting approval.
uint32 total_pending = 3;
// Brief description of what changed.
string summary = 4;
}
// Submit analysis results from sandbox to gateway.
message SubmitPolicyAnalysisRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 6;
// Aggregated denial summaries.
repeated DenialSummary summaries = 1;
// Proposed policy chunks (validated by sandbox OPA engine).
repeated PolicyChunk proposed_chunks = 2;
// Analysis mode. `mechanistic` is the observation-driven path from the
// denial aggregator — chunks targeting the same host|port|binary fold
// into one row with hit_count incremented. `agent_authored` is an
// intentional proposal from an in-sandbox agent — each submission lands
// as its own chunk so the redraft-after-rejection loop has a stable id
// to watch. Other values are treated as agent-style (no dedup) so a new
// mode does not silently collapse proposals.
string analysis_mode = 3;
// Sandbox name. The authenticated sandbox principal remains authoritative
// for this internal callback.
string name = 4;
// Anonymous network activity counters.
repeated NetworkActivitySummary network_activity_summaries = 5;
}
message SubmitPolicyAnalysisResponse {
// Number of chunks accepted by the gateway.
uint32 accepted_chunks = 1;
// Number of chunks rejected by gateway validation.
uint32 rejected_chunks = 2;
// Reasons for each rejected chunk.
repeated string rejection_reasons = 3;
// Server-assigned chunk IDs for the accepted chunks, in submission order.
// Agents use these to watch proposal state via policy.local's
// GET /v1/proposals/{id} and /wait endpoints.
repeated string accepted_chunk_ids = 4;
}
// Get draft policy for a sandbox.
message GetDraftPolicyRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
// Optional status filter: "pending", "approved", "rejected", or "" for all.
string status_filter = 2;
string sandbox = 1;
}
message GetDraftPolicyResponse {
reserved 4;
reserved "last_analyzed_at_ms";
// Draft policy chunks.
repeated PolicyChunk chunks = 1;
// LLM-generated summary of all analysis (empty in mechanistic mode).
string rolling_summary = 2;
// Current draft version.
uint64 draft_version = 3;
// Time when the last analysis completed.
google.protobuf.Timestamp last_analyzed_time = 104;
}
// Approve a single draft chunk.
message ApproveDraftChunkRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
// Chunk ID to approve.
string chunk_id = 2;
// Token returned with the reviewed PolicyChunk. Approval fails with
// FAILED_PRECONDITION if live decision inputs no longer match it.
string review_token = 4;
string sandbox = 1;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 5;
}
message ApproveDraftChunkResponse {
// New policy version after merge.
uint32 policy_version = 1;
// SHA-256 hash of the new policy.
string policy_hash = 2;
}
// Reject a single draft chunk.
message RejectDraftChunkRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
// Chunk ID to reject.
string chunk_id = 2;
// Optional reason for rejection (fed to LLM context in future analysis).
string reason = 3;
string sandbox = 1;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 5;
}
message RejectDraftChunkResponse {}
// Approve all pending chunks.
message DraftChunkApproval {
string chunk_id = 1;
string review_token = 2;
}
message ApproveAllDraftChunksRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
// Include chunks with security_notes (default false: skips them).
bool include_security_flagged = 2;
// Exact reviewed chunks and tokens. The server validates them against one
// live snapshot, stages compatible operations in order, and writes once.
repeated DraftChunkApproval approvals = 3;
string sandbox = 1;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 5;
}
message ApproveAllDraftChunksResponse {
// New policy version after merge.
uint32 policy_version = 1;
// SHA-256 hash of the new policy.
string policy_hash = 2;
// Number of chunks approved.
uint32 chunks_approved = 3;
// Number of chunks skipped for any reason, including security flags,
// stale or invalid candidates, and conflicts within the staged batch.
uint32 chunks_skipped = 4;
}
// Edit a pending chunk in-place.
message EditDraftChunkRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
// Chunk ID to edit.
string chunk_id = 2;
// The modified rule (replaces existing proposed_rule).
openshell.sandbox.v1.NetworkPolicyRule proposed_rule = 3;
string sandbox = 1;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 5;
}
message EditDraftChunkResponse {}
// Reverse an approval (remove merged rule from active policy).
message UndoDraftChunkRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
// Chunk ID to undo.
string chunk_id = 2;
string sandbox = 1;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 4;
}
message UndoDraftChunkResponse {
// New policy version after removal.
uint32 policy_version = 1;
// SHA-256 hash of the updated policy.
string policy_hash = 2;
}
// Clear all pending draft chunks for a sandbox.
message ClearDraftChunksRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
string sandbox = 1;
// Optional nonzero UUID for durable at-most-once admission. Successful results
// can be replayed for 24 hours; see the API errors and retries reference.
string request_id = 3;
}
message ClearDraftChunksResponse {
// Number of chunks cleared.
uint32 chunks_cleared = 1;
}
// Get decision history for a sandbox's draft policy.
message GetDraftHistoryRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 2;
string sandbox = 1;
}
message DraftHistoryEntry {
reserved 1;
reserved "timestamp_ms";
// Time when the event occurred.
google.protobuf.Timestamp event_time = 101;
// Event type: "denial_detected", "analysis_cycle", "approved",
// "rejected", "edited", "undone", "cleared".
string event_type = 2;
// Human-readable description.
string description = 3;
// Associated chunk ID (if applicable).
string chunk_id = 4;
}
message GetDraftHistoryResponse {
// Chronological decision history.
repeated DraftHistoryEntry entries = 1;
}
// ---------------------------------------------------------------------------
// Workspace messages
// ---------------------------------------------------------------------------
// Create workspace request.
message CreateWorkspaceRequest {
// Workspace name. Must be a valid DNS-1123 label.
string name = 1;
// Optional labels for the workspace (key-value metadata).
map<string, string> labels = 2;
// Optional nonzero UUID. Same ID and payload replay success for 24 hours.
string request_id = 3;
}
// Create workspace response.
message CreateWorkspaceResponse {
openshell.datamodel.v1.Workspace workspace = 1;
}
// Get workspace request.
message GetWorkspaceRequest {
// Workspace name (canonical lookup key).
string name = 1;
}
// Get workspace response.
message GetWorkspaceResponse {
openshell.datamodel.v1.Workspace workspace = 1;
}
// List workspaces request.
message ListWorkspacesRequest {
// The maximum number of workspaces to return. Zero uses 100. Values above
// 1000 are coerced to 1000; negative values are invalid.
int32 page_size = 1;
// Token from a previous ListWorkspaces response. All other request parameters
// except page_size must match the request that produced it.
string page_token = 2;
// Optional label selector for filtering (format: "key1=value1,key2=value2").
string label_selector = 3;
}
// List workspaces response.
message ListWorkspacesResponse {
repeated openshell.datamodel.v1.Workspace workspaces = 1;
// Token for the next page. Empty when there are no subsequent pages.
string next_page_token = 2;
}
// Delete workspace request.
message DeleteWorkspaceRequest {
// Workspace name (canonical lookup key).
string name = 1;
bool allow_missing = 2;
// Optional nonzero UUID. Same ID and payload replay success for 24 hours.
string request_id = 3;
}
// Delete workspace response.
message DeleteWorkspaceResponse {
reserved 1;
reserved "deleted";
DeletionOutcome outcome = 2;
}
// ---------------------------------------------------------------------------
// Workspace membership messages
// ---------------------------------------------------------------------------
// Workspace-scoped role for members.
enum WorkspaceRole {
WORKSPACE_ROLE_UNSPECIFIED = 0;
WORKSPACE_ROLE_USER = 1;
WORKSPACE_ROLE_ADMIN = 2;
}
// Stable recovery action for the most recent provider credential refresh
// failure. Kept after the pre-existing enums so adding it does not renumber
// their generated descriptors. Clients should use this field instead of
// parsing last_error.
enum ProviderCredentialRefreshRecoveryAction {
PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_UNSPECIFIED = 0;
PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_RETRY = 1;
PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_REAUTHORIZE = 2;
PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_FIX_CONFIGURATION = 3;
PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_INVESTIGATE = 4;
}
// Policy applied when the canonical main process exits outside an intentional
// stop or delete operation. Kept after existing enums so generated enum
// descriptors remain stable.
enum SandboxRestartPolicy {
SANDBOX_RESTART_POLICY_UNSPECIFIED = 0;
SANDBOX_RESTART_POLICY_NEVER = 1;
SANDBOX_RESTART_POLICY_ON_FAILURE = 2;
SANDBOX_RESTART_POLICY_ALWAYS = 3;
}
// Workspace membership record.
message WorkspaceMember {
openshell.datamodel.v1.ObjectMeta metadata = 1;
// OIDC subject claim identifying the principal.
string principal_subject = 2;
// Role assigned to the principal within the workspace.
WorkspaceRole role = 3;
}
// Add workspace member request.
message AddWorkspaceMemberRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 1;
// OIDC subject claim identifying the principal.
string principal_subject = 2;
// Role to assign.
WorkspaceRole role = 3;
// Optional nonzero UUID. Same ID and payload replay success for 24 hours.
string request_id = 4;
}
// Add workspace member response.
message AddWorkspaceMemberResponse {
WorkspaceMember member = 1;
}
// Remove workspace member request.
message RemoveWorkspaceMemberRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 1;
// OIDC subject claim identifying the principal to remove.
string principal_subject = 2;
bool allow_missing = 3;
// Optional nonzero UUID. Same ID and payload replay success for 24 hours.
string request_id = 4;
}
// Remove workspace member response.
message RemoveWorkspaceMemberResponse {
reserved 1;
reserved "removed";
DeletionOutcome outcome = 2;
}
// Result of a public delete, membership removal, or session revocation.
// Default requests return NOT_FOUND for a missing target. With allow_missing,
// only a missing target becomes ALREADY_ABSENT; parent lookup, authorization,
// validation, precondition, and backend errors retain their normal status.
// These results describe the targeted resource, not a same-name replacement.
enum DeletionOutcome {
// No outcome was supplied. Never infer completion from this value.
DELETION_OUTCOME_UNSPECIFIED = 0;
// The targeted gateway resource is removed (or the SSH session is revoked).
// Downstream platform garbage collection may still be finishing.
DELETION_OUTCOME_COMPLETED = 1;
// Sandbox deletion is accepted but its gateway record still exists.
// Observe the targeted sandbox ID until it disappears for completion.
DELETION_OUTCOME_ACCEPTED = 2;
// The target did not exist and allow_missing was true.
DELETION_OUTCOME_ALREADY_ABSENT = 3;
}
// List workspace members request.
message ListWorkspaceMembersRequest {
// Workspace scope. Only a named workspace selection is accepted.
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 1;
// The maximum number of members to return. Zero uses 100. Values above
// 1000 are coerced to 1000; negative values are invalid.
int32 page_size = 2;
// Token from a previous ListWorkspaceMembers response. All other request
// parameters except page_size must match the request that produced it.
string page_token = 3;
}
// List workspace members response.
message ListWorkspaceMembersResponse {
repeated WorkspaceMember members = 1;
// Token for the next page. Empty when there are no subsequent pages.
string next_page_token = 2;
}
// Short-lived credential for one policy-authorized extension service.
// Kept at the end of the file so adding it does not renumber existing
// generated message descriptors.
message ExtensionServiceCredential {
reserved 3;
reserved "expires_at_ms";
// Operator registration name used to correlate the credential with the
// stable service registration delivered by GetSandboxConfig.
string service_name = 1;
// Gateway-minted JWT with an audience derived from the registration.
string token = 2 [(openshell.options.v1.secret) = true];
// Absolute expiry of the token.
google.protobuf.Timestamp expiration_time = 103;
}
// Last observed network result for a configured external tool endpoint.
// Results describe accepted traffic observations, not present availability.
enum EndpointResult {
ENDPOINT_RESULT_UNSPECIFIED = 0;
// No exchange has been observed under the current configuration and session.
ENDPOINT_RESULT_NO_OBSERVED_EXCHANGE = 1;
// An upstream HTTP status below 400 was received. Its body can still contain
// an MCP error; this result does not establish tool-call success.
ENDPOINT_RESULT_HTTP_RESPONSE_RECEIVED = 2;
// OpenShell policy denied the request locally.
ENDPOINT_RESULT_POLICY_DENIED = 3;
// An applicable OpenShell-managed credential was unavailable.
ENDPOINT_RESULT_CREDENTIAL_UNAVAILABLE = 4;
// TLS setup for the upstream connection failed.
ENDPOINT_RESULT_TLS_FAILED = 5;
// The upstream transport failed before an HTTP response arrived.
ENDPOINT_RESULT_TRANSPORT_FAILED = 6;
// The upstream service returned an HTTP rejection.
ENDPOINT_RESULT_UPSTREAM_REJECTED = 7;
}
// One redacted endpoint result in a supervisor's complete status report.
message EndpointObservation {
// Stable identifier derived from the configured host, ports, and path.
string endpoint_id = 1;
// Latest result under the reported configuration and supervisor session.
EndpointResult result = 2;
}
// Complete endpoint status report for the caller's current configuration.
message ReportEndpointStatusRequest {
// Sandbox id. Must match the authenticated sandbox principal.
string sandbox_id = 1;
// Hash from the active effective policy delivered by the gateway.
string policy_hash = 2;
// Provider environment revision delivered with the active configuration.
uint64 provider_env_revision = 3;
// Exactly one result for every distinct observed endpoint in the policy.
repeated EndpointObservation observations = 4;
// Endpoints with a new observation in this batch. Omitted endpoints retain
// their prior report time; an identical retry never advances report time.
repeated string observed_endpoint_ids = 5;
// Active ConnectSupervisor session that owns these observations.
string supervisor_session_id = 6;
// Monotonically increasing sequence within the authenticated session. Gaps
// are allowed when an inventory reset supersedes a frozen snapshot. Retrying
// a report preserves its complete body and sequence for idempotent acknowledgement.
uint64 report_sequence = 7;
}
// Empty acknowledgement for a persisted endpoint status report.
message ReportEndpointStatusResponse {}
// A configured endpoint and its last accepted network result in one record.
// Address fields contain policy selectors, never request URLs or credentials.
message EndpointStatus {
reserved 6;
reserved "last_reported_at";
// Stable identifier for selecting this endpoint without parsing display text.
string endpoint_id = 1;
// Lowercase configured endpoint host.
string host = 2;
// Sorted, deduplicated effective endpoint ports. Validated endpoints have at
// least one port.
repeated uint32 ports = 3;
// Canonical configured path selector; an unrestricted path is /**.
string path = 4;
// Last accepted result, aggregated across configured callers and ports.
// NoObservedExchange retains the address and has no report timestamp.
EndpointResult last_result = 5;
// Time when the gateway accepted the observation. This is not the request
// time: still-valid evidence can be reaccepted after a reset. Identical
// same-sequence retries do not advance it. Absent until a result is reported.
google.protobuf.Timestamp last_reported_time = 106;
}
// Durable provisioning attempt, independent of supervisor registration and polling.
message SandboxProvisioning {
string attempt_id = 1;
string configuration_change_id = 2;
google.protobuf.Timestamp configuration_change_time = 3;
google.protobuf.Timestamp first_rejection_time = 4;
// Active preparation or admission-repair deadline. Absent after Ready/timeout.
google.protobuf.Timestamp deadline = 5;
google.protobuf.Timestamp timeout_time = 6;
// Set only after both supervisor and workload compute have been reclaimed.
google.protobuf.Timestamp cleanup_completed_time = 7;
// A safe gateway-authored diagnostic; never a raw driver error.
string cleanup_error = 8;
// Durable backoff for interrupted or failed reclamation.
google.protobuf.Timestamp cleanup_retry_time = 9;
// Attachment edits have their own durable clock; status writes do not change it.
string attachment_change_id = 10;
google.protobuf.Timestamp attachment_change_time = 11;
// Absolute ceiling for image preparation and initial supervisor startup.
// Set once per new attempt; progress, configuration writes, and restarts
// cannot extend it. Absent on attempts created before preparation timing.
google.protobuf.Timestamp preparation_deadline = 12;
// First authenticated supervisor configuration report for this attempt.
// Starts the admission repair window. Absent while preparing, including
// after a preparation timeout. Duplicate reports never change this value.
google.protobuf.Timestamp admission_start_time = 13;
// An owned driver create/start future is still pending. Claimed before
// dispatch and cleared on its actual success or error response. Monitor
// interruption, owner loss, and StopSandbox/NotFound do not clear it.
// While set, cleanup cannot complete and retry/deletion remain blocked.
// This does not strengthen a driver's transport-error settlement contract.
bool driver_operation_pending = 14;
// Unique for each claimed driver operation, including recovery and automatic
// restart. Retained after settlement to reject delayed callbacks from an
// earlier operation on the same attempt and runtime generation.
string driver_operation_id = 15;
}
// Create-time request to expose one loopback HTTP service in a sandbox.
message SandboxServiceExposure {
// Service name within the sandbox. Empty selects the unnamed endpoint.
string service = 1;
// Loopback TCP port inside the sandbox.
uint32 target_port = 2;
// Application authorization behavior. Omission resolves to STRIP.
ServiceAuthorizationMode authorization_mode = 3;
}
// Controls whether an exposed service receives the request's application
// Authorization header.
enum ServiceAuthorizationMode {
// Omission preserves the secure legacy behavior and resolves to STRIP.
SERVICE_AUTHORIZATION_MODE_UNSPECIFIED = 0;
// Remove Authorization before forwarding to the sandbox service.
SERVICE_AUTHORIZATION_MODE_STRIP = 1;
// Forward one syntactically valid bearer Authorization header unchanged.
SERVICE_AUTHORIZATION_MODE_BEARER_PASSTHROUGH = 2;
}