From 284da54de5c7710482c553eb15a8aa020744e223 Mon Sep 17 00:00:00 2001 From: Johnny Greco Date: Wed, 5 Aug 2026 15:01:50 -0400 Subject: [PATCH 1/8] docs(readme): add theme-aware banner (#2619) * docs(readme): add theme-aware banner Signed-off-by: Johnny Greco * docs(readme): exclude preview screenshot from tree Signed-off-by: Johnny Greco --------- Signed-off-by: Johnny Greco --- README.md | 10 +++++++++- docs/brand/assets/openshell-banner-dark.png | Bin 0 -> 71417 bytes docs/brand/assets/openshell-banner-light.png | Bin 0 -> 70414 bytes 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 docs/brand/assets/openshell-banner-dark.png create mode 100644 docs/brand/assets/openshell-banner-light.png diff --git a/README.md b/README.md index 3d5aeb6fb..4b8c37015 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,12 @@ -# ![OpenShell](docs/brand/assets/openshell-lockup-horizontal.svg) + + + + + + OpenShell + + + [![License](https://img.shields.io/badge/License-Apache_2.0-blue)](https://github.com/NVIDIA/OpenShell/blob/main/LICENSE) [![PyPI](https://img.shields.io/badge/PyPI-openshell-orange?logo=pypi)](https://pypi.org/project/openshell/) diff --git a/docs/brand/assets/openshell-banner-dark.png b/docs/brand/assets/openshell-banner-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6c7d11f6e4d0f383d67c06eb912d014f36516d51 GIT binary patch literal 71417 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 K7`K14X~qBOl|v%{ literal 0 HcmV?d00001 diff --git a/docs/brand/assets/openshell-banner-light.png b/docs/brand/assets/openshell-banner-light.png new file mode 100644 index 0000000000000000000000000000000000000000..c2a951a7d7411619ef76216bbb64fa2b9ee6c11d GIT binary patch literal 70414 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 TSvKQ5pa_GftDnm{r-UW|GYNJB literal 0 HcmV?d00001 From c5f8366cd31860e5d6f00480d4fc01f8cc3b3c0f Mon Sep 17 00:00:00 2001 From: Roland Huss Date: Wed, 5 Aug 2026 22:25:44 +0200 Subject: [PATCH 2/8] feat(sdk/go): add Go SDK foundation, types, and sandbox client (A) (#2271) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(sdk/go): add Go SDK foundation, types, and sandbox client (A) Add the Go SDK module with the full API contract and a working sandbox client as the first vertical slice. All other resource clients are present as stubs returning Unimplemented errors, to be replaced with real implementations in subsequent PRs. Contents: - Module setup (go.mod, Makefile, mise.toml) - All domain types (types/ package) - Full ClientInterface with all sub-client accessors - Shared infrastructure (errors, auth, gRPC connection, logging) - Sandbox client with converter and tests (fully functional) - Stub clients for remaining resources (exec, file, health, provider, profile, config, refresh, policy, service, ssh, tcp) Part of the Go SDK decomposition plan (#2270). Implements #2044. * fix(sdk/go): address review feedback on PR #2271 - Make scheme parsing drive transport selection: http:// uses plaintext gRPC, https:// or no scheme uses TLS. Add regression tests. - Add Resources and DriverConfig fields to SandboxTemplate and update both converter directions (SandboxFromProto/SandboxSpecToProto). - Regenerate proto bindings from current canonical proto sources to eliminate drift (SigV4/MCP fields, params matchers, reserved fields). - Run gofmt/goimports on all handwritten Go files. Signed-off-by: Roland Huß * fix(sdk/go): address principal engineer review findings - Remove dead boolCount function that would fail golangci-lint (#1) - Emit EventAdded for the first watch event instead of EventModified, matching k8s watch semantics (#7) - Add mutex locking to all mock server methods that access the shared sandboxes map, fixing latent race conditions (#12) - Skip HealthCheck integration test that calls an unimplemented stub (#13) - Scope doc.go examples: mark sections for sub-clients not yet available in this PR with "available in a future release" (#4) - Document Config.Timeout/RetryPolicy/Logger and WatchOptions fields as reserved for future use (#2, #6) Signed-off-by: Roland Huß * refactor(sdk/go): migrate mise config to centralized task include Move Go SDK mise configuration from standalone sdk/go/mise.toml into the project's centralized pattern: - Add Go tools (go, golangci-lint, protoc-gen-go, protoc-gen-go-grpc) to root mise.toml [tools] section - Create tasks/go.toml with all SDK tasks using go: namespace prefix and dir=sdk/go for working directory - Update sdk/go/Makefile to reference namespaced task names - Update proto:sync default path for monorepo layout Addresses review feedback from drew on PR #2271 regarding mise convention alignment. Signed-off-by: Roland Huß * refactor(sdk/go): remove UPSTREAM_VERSION standalone repo artifact Remove sdk/go/proto/UPSTREAM_VERSION file and its exclusion from proto:check. This was a leftover from the standalone repo prototype. In a monorepo, proto drift is detectable via git diff between sdk/go/proto/ and proto/ directly. Signed-off-by: Roland Huß * refactor(sdk/go): switch proto generation from protoc to buf Replace raw protoc invocations with buf for Go SDK proto code generation, aligning with the TS SDK approach (PR #2122). - Add repo-level buf.yaml declaring proto/ as the buf module with lint and breaking change detection config - Add sdk/go/buf.gen.yaml configuring buf to generate Go code directly from root proto/ (no more vendored .proto copies) - Delete vendored .proto source files from sdk/go/proto/ - Rewrite go:proto:gen and go:proto:check mise tasks to use buf - Remove go:proto:sync and go:proto:clean tasks (no longer needed) - Add proto target to sdk/go/Makefile - Add buf 1.72.0 to root mise.toml tool dependencies - Include options.proto in generation (was stripped from vendored copies) - Regenerate all .pb.go files via the new buf pipeline Signed-off-by: Roland Huß * test(sdk/go): add proto-converter field coverage detection Use protobuf reflection to enumerate all fields on key proto messages (SandboxSpec, SandboxTemplate, SandboxStatus, SandboxCondition, SandboxPolicy) and compare against explicit handled/skipped sets in the converter tests. Unhandled fields produce warnings (t.Log), not failures, so proto contributors are not forced to fix SDK converters in the same PR. Stale entries in the handled set (removed proto fields) do fail, since they indicate the converter references something that no longer exists. A follow-up CI workflow will create GitHub issues when converter drift lands on main. Signed-off-by: Roland Huß * fix(sdk/go): bump Go to 1.26 and fix errcheck lint violations The upstream go.mod now has `toolchain go1.26.4`, which requires Go 1.26 to build golangci-lint. Bump the mise.toml Go version from 1.25 to 1.26 and wrap deferred Close() calls in test helpers to satisfy errcheck. Assisted-By: 🤖 Claude Code * feat(sdk/go): add ObjectMeta fields (annotations, workspace, deletion_timestamp) Add three new proto ObjectMeta fields to Sandbox and Provider domain types: Annotations (map), Workspace (string), and DeletionTimestamp (*time.Time). Update converters in both directions, deep-copy maps at the proto/SDK boundary, and add TimeFromMillisPtr/MillisFromTimePtr helper functions. Assisted-By: 🤖 Claude Code * chore(sdk/go): regenerate proto bindings after rebase Pick up workspace fields from upstream PR #2445 (Wire authorization into workspace model). All request messages now include workspace parameter in the generated Go bindings. Assisted-By: 🤖 Claude Code * feat(sdk/go): add workspace scoping to all RPC interfaces Add workspace parameter to every sandbox-scoped RPC method across all interfaces (Sandbox, Exec, File, Service, SSH, TCP, Config, Policy, Provider, Profile, Refresh). The workspace string is passed as the second parameter after ctx, following the convention workspace then resource-name. Key changes: - SandboxInterface: all 10 methods gain workspace parameter - sandbox_client.go: passes Workspace field in every proto request - ListOptions: add AllWorkspaces field for cross-workspace queries - All stub interfaces updated to match new signatures - All sandbox client tests updated with "default" workspace Assisted-By: 🤖 Claude Code * chore(sdk/go): remove coverage.out from tracking Assisted-By: 🤖 Claude Code * fix(sdk/go): address review feedback from mrunalp - Add RefreshStrategyAWSStsAssumeRole to match proto enum value 6, fulfilling the "all domain types upfront" contract - Wrap context.DeadlineExceeded and context.Canceled in StatusError so IsDeadlineExceeded() and IsCancelled() helpers work correctly - Return error from mapToStruct/SandboxSpecToProto instead of silently discarding structpb.NewStruct failures on invalid template maps Signed-off-by: Roland Huss * fix(sdk/go): address remaining review items - Wire go:ci into root ci task so SDK is tested in repository CI - Fix gofmt formatting on converter files - Add goimports to mise.toml tools - Add coverage.out to .gitignore - Add Go SDK section to AGENTS.md and CONTRIBUTING.md - Add regression tests for context-error wrapping (IsDeadlineExceeded, IsCancelled) and invalid template map rejection - Remove panic from SandboxToProto, return error instead Signed-off-by: Roland Huss * fix(sdk/go): pin goimports version and update lockfile Pin goimports to 0.48.0 instead of "latest" and regenerate mise.lock to include the new entry. Signed-off-by: Roland Huss * fix(sdk/go): TLS.Insecure means skip-verify, not plaintext Align TLS.Insecure semantics with the Rust SDK: Insecure: true now uses TLS with InsecureSkipVerify (skip cert verification) instead of switching to plaintext. Only the http:// scheme triggers plaintext. This fixes token auth against dev/k3d gateways: StaticToken and RefreshableToken require transport security, which real TLS (even with InsecureSkipVerify) satisfies, but plaintext does not. For http:// + token auth (dev gateways without TLS), wrap the auth provider to override RequireTransportSecurity, matching the Rust SDK's behavior where http:// accepts any auth mode. Transport decision table (matches Rust SDK crates/openshell-sdk): http:// + any TLS config -> plaintext (TLS config ignored) https:// + Insecure: true -> TLS, skip cert verify https:// + Insecure: false -> TLS, full verification no scheme -> same as https:// Signed-off-by: Roland Huss * feat(sdk/go): add missing policy proto fields Add 6 previously silently dropped fields to the network policy types and converters, preventing security-relevant data loss on round-trip: NetworkEndpoint fields 19-23: - CredentialSigning: SigV4 re-signing mode - SigningService: AWS service name for SigV4 - SigningRegion: AWS region override for SigV4 - JsonRpcMaxBodyBytes: JSON-RPC body inspection limit - Mcp: MCP-specific policy options (new McpOptions type) L7Allow and L7DenyRule field 9: - Params: MCP params matcher map for tools/call filtering New type McpOptions with StrictToolNames and AllowAllKnownMcpMethods optional booleans matching the proto definitions. Signed-off-by: Roland Huss * fix(sdk/go): enforce coverage test and extend to policy messages Change coverage_test.go from t.Logf (silent) to t.Errorf so that unhandled proto fields fail the test immediately. Add coverage tests for NetworkEndpoint (23 fields), L7Allow (8 fields), L7DenyRule (8 fields), and McpOptions (2 fields). Any new proto field that is not in the handled set or explicitly skipped now breaks the build, closing the silent-drift gap. Signed-off-by: Roland Huss * ci(sdk/go): add Go SDK job to branch-checks workflow Add a Go SDK job to branch-checks.yml that runs mise run go:ci (lint, build, test, proto-check, docs-check) on every PR. This ensures the SDK is tested in CI, not just locally. Signed-off-by: Roland Huss * fix(sdk/go): address should-fix review items #6 Fix broken godoc examples: add workspace parameter to all method calls in doc.go that were broken after workspace scoping. #7 Add Err field to Event[T]: Watch error events now carry the underlying error instead of discarding it. #8 Separate Unauthenticated from PermissionDenied: add ErrorUnauthenticated code and IsUnauthenticated() helper. gRPC Unauthenticated (401) now maps to its own code instead of collapsing into PermissionDenied (403). #9 Add Unwrap to StatusError: replace dead Details field with Cause error field. StatusError.Unwrap() returns Cause, enabling errors.Is/As unwrapping. FromGRPCError and contextError both populate Cause. Signed-off-by: Roland Huss * ci(sdk/go): add go:format:check to CI pipeline Add gofmt format verification to go:ci. Catches unformatted Go files before they reach the PR. Fix formatting on coverage_test.go. Signed-off-by: Roland Huss * chore(sdk/go): remove Makefile in favor of mise tasks All build, lint, test, and proto-gen tasks are already defined in tasks/go.toml and invoked via mise. The Makefile was a leftover that duplicated this and raised questions in review. Signed-off-by: Roland Huß * feat(sdk/go): sync proto bindings and add credential handle support Regenerate Go proto bindings after rebase to pick up new CredentialHandle message and Provider.credential_handles and profile_workspace fields from upstream. Add domain types, converter support, and proto field coverage tests for Provider and CredentialHandle. Signed-off-by: Roland Huß * fix(sdk/go): reject plaintext auth leak and fix watch error handling Reject http:// addresses when the auth provider requires transport security instead of silently stripping the requirement. Remove the insecureAuthWrapper that overrode RequireTransportSecurity. Fix watch stream error handling: use blocking send for terminal errors so they are never silently dropped when the channel is full, and wrap mid-stream errors with converter.FromGRPCError so SDK error helpers like IsUnavailable work on watch Event.Err. Signed-off-by: Roland Huß * fix(sdk/go): address review findings from multi-agent code review - WaitReady now detects SandboxDeleting phase and returns immediately instead of polling indefinitely - Watch goroutine defers streamCancel() to prevent context leaks - Fix StopOnTerminal=false test to keep stream open (was wrong-reason pass due to stream ending, not StopOnTerminal logic) - Add EventDeleted test covering the Deleting phase branch - Add provider converter unit tests for CredentialHandle round-trip, nil handling, and empty maps Signed-off-by: Roland Huß --------- Signed-off-by: Roland Huß Signed-off-by: Roland Huss --- .github/workflows/branch-checks.yml | 19 + .gitignore | 1 + AGENTS.md | 9 + CONTRIBUTING.md | 1 + buf.yaml | 31 + mise.lock | 66 + mise.toml | 6 + sdk/go/buf.gen.yaml | 36 + sdk/go/go.mod | 22 + sdk/go/go.sum | 50 + sdk/go/openshell/v1/auth.go | 48 + sdk/go/openshell/v1/auth_extra.go | 80 + sdk/go/openshell/v1/auth_extra_test.go | 177 + sdk/go/openshell/v1/auth_refresh.go | 137 + sdk/go/openshell/v1/auth_refresh_test.go | 373 + sdk/go/openshell/v1/auth_test.go | 36 + sdk/go/openshell/v1/client.go | 142 + sdk/go/openshell/v1/client_test.go | 61 + sdk/go/openshell/v1/config.go | 76 + sdk/go/openshell/v1/doc.go | 358 + sdk/go/openshell/v1/errors.go | 60 + sdk/go/openshell/v1/errors_test.go | 127 + sdk/go/openshell/v1/exec.go | 40 + sdk/go/openshell/v1/file.go | 13 + sdk/go/openshell/v1/grpc_errors.go | 22 + sdk/go/openshell/v1/health.go | 18 + sdk/go/openshell/v1/integration_test.go | 76 + .../openshell/v1/internal/converter/copy.go | 65 + .../v1/internal/converter/coverage_test.go | 234 + .../openshell/v1/internal/converter/errors.go | 54 + .../v1/internal/converter/errors_test.go | 122 + sdk/go/openshell/v1/internal/converter/log.go | 47 + .../v1/internal/converter/log_test.go | 97 + .../v1/internal/converter/network_policy.go | 332 + .../openshell/v1/internal/converter/policy.go | 304 + .../v1/internal/converter/provider.go | 101 + .../v1/internal/converter/provider_test.go | 174 + .../v1/internal/converter/sandbox.go | 206 + .../v1/internal/converter/sandbox_test.go | 404 + .../openshell/v1/internal/converter/time.go | 43 + .../v1/internal/converter/time_test.go | 73 + sdk/go/openshell/v1/internal/grpc/conn.go | 96 + .../openshell/v1/internal/grpc/conn_test.go | 101 + sdk/go/openshell/v1/logger.go | 12 + sdk/go/openshell/v1/options.go | 32 + sdk/go/openshell/v1/policy.go | 173 + sdk/go/openshell/v1/profile.go | 70 + sdk/go/openshell/v1/provider.go | 29 + sdk/go/openshell/v1/refresh.go | 42 + sdk/go/openshell/v1/sandbox.go | 73 + sdk/go/openshell/v1/sandbox_client.go | 291 + sdk/go/openshell/v1/sandbox_client_test.go | 1068 ++ sdk/go/openshell/v1/service.go | 25 + sdk/go/openshell/v1/ssh.go | 58 + sdk/go/openshell/v1/stub_clients.go | 195 + sdk/go/openshell/v1/tcp.go | 97 + sdk/go/openshell/v1/types.go | 46 + sdk/go/openshell/v1/types/auth.go | 13 + sdk/go/openshell/v1/types/config.go | 19 + sdk/go/openshell/v1/types/doc.go | 10 + sdk/go/openshell/v1/types/errors.go | 134 + sdk/go/openshell/v1/types/exec.go | 17 + sdk/go/openshell/v1/types/health.go | 10 + sdk/go/openshell/v1/types/log.go | 98 + sdk/go/openshell/v1/types/logger.go | 12 + sdk/go/openshell/v1/types/network_policy.go | 170 + sdk/go/openshell/v1/types/options.go | 48 + sdk/go/openshell/v1/types/policy.go | 341 + sdk/go/openshell/v1/types/profile.go | 93 + sdk/go/openshell/v1/types/provider.go | 38 + sdk/go/openshell/v1/types/refresh.go | 44 + sdk/go/openshell/v1/types/sandbox.go | 76 + sdk/go/openshell/v1/types/service.go | 15 + sdk/go/openshell/v1/types/setting.go | 115 + sdk/go/openshell/v1/types/ssh.go | 34 + sdk/go/openshell/v1/types/types.go | 54 + sdk/go/openshell/v1/types/watch.go | 18 + sdk/go/openshell/v1/types_reexport.go | 57 + sdk/go/openshell/v1/watch.go | 46 + sdk/go/openshell/v1/watch_test.go | 134 + sdk/go/proto/datamodelv1/datamodel.pb.go | 599 + sdk/go/proto/openshellv1/openshell.pb.go | 14464 ++++++++++++++++ sdk/go/proto/openshellv1/openshell_grpc.pb.go | 2719 +++ sdk/go/proto/optionsv1/options.pb.go | 204 + sdk/go/proto/sandboxv1/sandbox.pb.go | 2234 +++ tasks/ci.toml | 2 +- tasks/go.toml | 163 + 87 files changed, 28729 insertions(+), 1 deletion(-) create mode 100644 buf.yaml create mode 100644 sdk/go/buf.gen.yaml create mode 100644 sdk/go/go.mod create mode 100644 sdk/go/go.sum create mode 100644 sdk/go/openshell/v1/auth.go create mode 100644 sdk/go/openshell/v1/auth_extra.go create mode 100644 sdk/go/openshell/v1/auth_extra_test.go create mode 100644 sdk/go/openshell/v1/auth_refresh.go create mode 100644 sdk/go/openshell/v1/auth_refresh_test.go create mode 100644 sdk/go/openshell/v1/auth_test.go create mode 100644 sdk/go/openshell/v1/client.go create mode 100644 sdk/go/openshell/v1/client_test.go create mode 100644 sdk/go/openshell/v1/config.go create mode 100644 sdk/go/openshell/v1/doc.go create mode 100644 sdk/go/openshell/v1/errors.go create mode 100644 sdk/go/openshell/v1/errors_test.go create mode 100644 sdk/go/openshell/v1/exec.go create mode 100644 sdk/go/openshell/v1/file.go create mode 100644 sdk/go/openshell/v1/grpc_errors.go create mode 100644 sdk/go/openshell/v1/health.go create mode 100644 sdk/go/openshell/v1/integration_test.go create mode 100644 sdk/go/openshell/v1/internal/converter/copy.go create mode 100644 sdk/go/openshell/v1/internal/converter/coverage_test.go create mode 100644 sdk/go/openshell/v1/internal/converter/errors.go create mode 100644 sdk/go/openshell/v1/internal/converter/errors_test.go create mode 100644 sdk/go/openshell/v1/internal/converter/log.go create mode 100644 sdk/go/openshell/v1/internal/converter/log_test.go create mode 100644 sdk/go/openshell/v1/internal/converter/network_policy.go create mode 100644 sdk/go/openshell/v1/internal/converter/policy.go create mode 100644 sdk/go/openshell/v1/internal/converter/provider.go create mode 100644 sdk/go/openshell/v1/internal/converter/provider_test.go create mode 100644 sdk/go/openshell/v1/internal/converter/sandbox.go create mode 100644 sdk/go/openshell/v1/internal/converter/sandbox_test.go create mode 100644 sdk/go/openshell/v1/internal/converter/time.go create mode 100644 sdk/go/openshell/v1/internal/converter/time_test.go create mode 100644 sdk/go/openshell/v1/internal/grpc/conn.go create mode 100644 sdk/go/openshell/v1/internal/grpc/conn_test.go create mode 100644 sdk/go/openshell/v1/logger.go create mode 100644 sdk/go/openshell/v1/options.go create mode 100644 sdk/go/openshell/v1/policy.go create mode 100644 sdk/go/openshell/v1/profile.go create mode 100644 sdk/go/openshell/v1/provider.go create mode 100644 sdk/go/openshell/v1/refresh.go create mode 100644 sdk/go/openshell/v1/sandbox.go create mode 100644 sdk/go/openshell/v1/sandbox_client.go create mode 100644 sdk/go/openshell/v1/sandbox_client_test.go create mode 100644 sdk/go/openshell/v1/service.go create mode 100644 sdk/go/openshell/v1/ssh.go create mode 100644 sdk/go/openshell/v1/stub_clients.go create mode 100644 sdk/go/openshell/v1/tcp.go create mode 100644 sdk/go/openshell/v1/types.go create mode 100644 sdk/go/openshell/v1/types/auth.go create mode 100644 sdk/go/openshell/v1/types/config.go create mode 100644 sdk/go/openshell/v1/types/doc.go create mode 100644 sdk/go/openshell/v1/types/errors.go create mode 100644 sdk/go/openshell/v1/types/exec.go create mode 100644 sdk/go/openshell/v1/types/health.go create mode 100644 sdk/go/openshell/v1/types/log.go create mode 100644 sdk/go/openshell/v1/types/logger.go create mode 100644 sdk/go/openshell/v1/types/network_policy.go create mode 100644 sdk/go/openshell/v1/types/options.go create mode 100644 sdk/go/openshell/v1/types/policy.go create mode 100644 sdk/go/openshell/v1/types/profile.go create mode 100644 sdk/go/openshell/v1/types/provider.go create mode 100644 sdk/go/openshell/v1/types/refresh.go create mode 100644 sdk/go/openshell/v1/types/sandbox.go create mode 100644 sdk/go/openshell/v1/types/service.go create mode 100644 sdk/go/openshell/v1/types/setting.go create mode 100644 sdk/go/openshell/v1/types/ssh.go create mode 100644 sdk/go/openshell/v1/types/types.go create mode 100644 sdk/go/openshell/v1/types/watch.go create mode 100644 sdk/go/openshell/v1/types_reexport.go create mode 100644 sdk/go/openshell/v1/watch.go create mode 100644 sdk/go/openshell/v1/watch_test.go create mode 100644 sdk/go/proto/datamodelv1/datamodel.pb.go create mode 100644 sdk/go/proto/openshellv1/openshell.pb.go create mode 100644 sdk/go/proto/openshellv1/openshell_grpc.pb.go create mode 100644 sdk/go/proto/optionsv1/options.pb.go create mode 100644 sdk/go/proto/sandboxv1/sandbox.pb.go create mode 100644 tasks/go.toml diff --git a/.github/workflows/branch-checks.yml b/.github/workflows/branch-checks.yml index fccdfd1bb..53f467ff7 100644 --- a/.github/workflows/branch-checks.yml +++ b/.github/workflows/branch-checks.yml @@ -218,6 +218,25 @@ jobs: - name: Test run: mise run test:python + go: + name: Go SDK + needs: pr_metadata + if: needs.pr_metadata.outputs.should_run == 'true' + runs-on: linux-amd64-cpu8 + container: + image: ghcr.io/nvidia/openshell/ci:latest + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Install tools + run: mise install --locked + + - name: Lint, build, test, proto-check + run: mise run go:ci + markdown: name: Markdown needs: pr_metadata diff --git a/.gitignore b/.gitignore index 40307c7a1..b6df45ef1 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,7 @@ pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports +coverage.out htmlcov/ .tox/ .nox/ diff --git a/AGENTS.md b/AGENTS.md index f2a9f486f..7e494a7b5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -204,6 +204,15 @@ ocsf_emit!(event); - `mise run e2e` — End-to-end tests against a running gateway. Run for infrastructure, sandbox, or policy changes. - `mise run ci` — Full local CI (lint + compile/type checks + tests). Run before opening a PR. +## Go SDK (`sdk/go/`) + +- The Go SDK lives in `sdk/go/` with module path `github.com/NVIDIA/OpenShell/sdk/go`. +- Run `mise run go:ci` for the full SDK CI pipeline (lint, build, test, proto-check, docs-check). +- Proto bindings are generated with `mise run go:proto:gen` from the `.proto` files in `proto/`. +- Domain types in `sdk/go/openshell/v1/types/` must not import proto packages. +- Converters in `sdk/go/openshell/v1/internal/converter/` deep-copy slices and maps at boundaries. +- Tests use bufconn for in-process gRPC and testify for assertions. + ## Python - Always use `uv` for Python commands (e.g., `uv pip install`, `uv run`, `uv venv`) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 22592c435..64b9d85b0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -434,6 +434,7 @@ Bazel does not yet cover `mise run gateway`, `mise run sandbox`, `mise run e2e`, | --------------- | --------------------------------------------- | | `crates/` | Rust crates | | `python/` | Python SDK and bindings | +| `sdk/go/` | Go SDK (types, gRPC clients, converters) | | `proto/` | Protocol buffer definitions | | `tasks/` | `mise` task definitions and build scripts | | `deploy/` | Dockerfiles, Helm chart, Kubernetes manifests | diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 000000000..a9ada8c9e --- /dev/null +++ b/buf.yaml @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Repo-level buf module. Declares proto/ as the single module so buf generate, +# buf lint, buf breaking, and the editor LSP all resolve imports the same way. +# Code generation lives with each consumer (see sdk/go/buf.gen.yaml, +# sdk/typescript/buf.gen.yaml); this file owns the module boundary and proto +# validation policy. +version: v2 +modules: + - path: proto +lint: + use: + - STANDARD + except: + # Flat proto/ layout: all files live in one directory with nested + # packages (openshell.v1, openshell.sandbox.v1, ...). Adopting these + # would require restructuring the tree into openshell//v1/ and + # updating every Rust/Python/TS codegen path and import. + - DIRECTORY_SAME_PACKAGE + - PACKAGE_DIRECTORY_MATCH + # Established API shape: services are unsuffixed (OpenShell, not + # OpenShellService) and RPCs reuse shared request/response messages with + # short names. Renaming these is a breaking change across the codebase. + - RPC_REQUEST_RESPONSE_UNIQUE + - RPC_REQUEST_STANDARD_NAME + - RPC_RESPONSE_STANDARD_NAME + - SERVICE_SUFFIX +breaking: + use: + - FILE diff --git a/mise.lock b/mise.lock index 5dc2cc25f..74067b6cf 100644 --- a/mise.lock +++ b/mise.lock @@ -1,5 +1,27 @@ # @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html +[[tools.buf]] +version = "1.72.0" +backend = "aqua:bufbuild/buf" + +[tools.buf."platforms.linux-arm64"] +checksum = "sha256:7641bd7e06a37a54cbb8c789f53465899def96196ab5c08057432f781a15d517" +url = "https://github.com/bufbuild/buf/releases/download/v1.72.0/buf-Linux-aarch64.tar.gz" +url_api = "https://api.github.com/repos/bufbuild/buf/releases/assets/480772526" +provenance = "minisign" + +[tools.buf."platforms.linux-x64"] +checksum = "sha256:a9c6186cf6fcf062b247345e1b7b12c26f580c1b2a4bbf4d3fe080abf85ceee8" +url = "https://github.com/bufbuild/buf/releases/download/v1.72.0/buf-Linux-x86_64.tar.gz" +url_api = "https://api.github.com/repos/bufbuild/buf/releases/assets/480772583" +provenance = "minisign" + +[tools.buf."platforms.macos-arm64"] +checksum = "sha256:be040ae0ca381103dfda68a36738695c4db3e48de8e91412acdc3d991f39b91e" +url = "https://github.com/bufbuild/buf/releases/download/v1.72.0/buf-Darwin-arm64.tar.gz" +url_api = "https://api.github.com/repos/bufbuild/buf/releases/assets/480772487" +provenance = "minisign" + [[tools."github:EmbarkStudios/cargo-about"]] version = "0.8.4" backend = "github:EmbarkStudios/cargo-about" @@ -104,6 +126,38 @@ checksum = "sha256:29caf036bdbb4e6f07afea31706b6f386cb5a4db9a46a3a8b462b9b78157e url = "https://github.com/rust-cross/cargo-zigbuild/releases/download/v0.22.3/cargo-zigbuild-aarch64-apple-darwin.tar.xz" url_api = "https://api.github.com/repos/rust-cross/cargo-zigbuild/releases/assets/405676922" +[[tools.go]] +version = "1.26.5" +backend = "core:go" + +[tools.go."platforms.linux-arm64"] +checksum = "sha256:fe4789e92b1f33358680864bbe8704289e7bb5fc207d80623c308935bd696d49" +url = "https://dl.google.com/go/go1.26.5.linux-arm64.tar.gz" + +[tools.go."platforms.linux-x64"] +checksum = "sha256:5c2c3b16caefa1d968a94c1daca04a7ca301a496d9b086e17ad77bb81393f053" +url = "https://dl.google.com/go/go1.26.5.linux-amd64.tar.gz" + +[tools.go."platforms.macos-arm64"] +checksum = "sha256:efb87ff28af9a188d0536ef5d42e63dd52ba8263cd7344a993cc48dd11dedb6a" +url = "https://dl.google.com/go/go1.26.5.darwin-arm64.tar.gz" + +[[tools."go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint"]] +version = "2.12.2" +backend = "go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint" + +[[tools."go:golang.org/x/tools/cmd/goimports"]] +version = "0.48.0" +backend = "go:golang.org/x/tools/cmd/goimports" + +[[tools."go:google.golang.org/grpc/cmd/protoc-gen-go-grpc"]] +version = "1.6.2" +backend = "go:google.golang.org/grpc/cmd/protoc-gen-go-grpc" + +[[tools."go:google.golang.org/protobuf/cmd/protoc-gen-go"]] +version = "1.36.11" +backend = "go:google.golang.org/protobuf/cmd/protoc-gen-go" + [[tools.helm]] version = "4.2.0" backend = "aqua:helm/helm" @@ -127,14 +181,17 @@ backend = "aqua:norwoodj/helm-docs" [tools.helm-docs."platforms.linux-arm64"] checksum = "sha256:c3787212332386dcd122debef7848feb165aa701467ae3e3442df7638f3ac4e4" url = "https://github.com/norwoodj/helm-docs/releases/download/v1.14.2/helm-docs_1.14.2_Linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/norwoodj/helm-docs/releases/assets/178327216" [tools.helm-docs."platforms.linux-x64"] checksum = "sha256:a8cf72ada34fad93285ba2a452b38bdc5bd52cc9a571236244ec31022928d6cc" url = "https://github.com/norwoodj/helm-docs/releases/download/v1.14.2/helm-docs_1.14.2_Linux_x86_64.tar.gz" +url_api = "https://api.github.com/repos/norwoodj/helm-docs/releases/assets/178327210" [tools.helm-docs."platforms.macos-arm64"] checksum = "sha256:2d8399db5b33d240d5f8985241bcf5483563150b968e3229823822979f3e4b8b" url = "https://github.com/norwoodj/helm-docs/releases/download/v1.14.2/helm-docs_1.14.2_Darwin_arm64.tar.gz" +url_api = "https://api.github.com/repos/norwoodj/helm-docs/releases/assets/178327215" [[tools.k3d]] version = "5.8.3" @@ -143,14 +200,17 @@ backend = "aqua:k3d-io/k3d" [tools.k3d."platforms.linux-arm64"] checksum = "sha256:0b8110f2229631af7402fb828259330985918b08fefd38b7f1b788a1c8687216" url = "https://github.com/k3d-io/k3d/releases/download/v5.8.3/k3d-linux-arm64" +url_api = "https://api.github.com/repos/k3d-io/k3d/releases/assets/229450023" [tools.k3d."platforms.linux-x64"] checksum = "sha256:dbaa79a76ace7f4ca230a1ff41dc7d8a5036a8ad0309e9c54f9bf3836dbe853e" url = "https://github.com/k3d-io/k3d/releases/download/v5.8.3/k3d-linux-amd64" +url_api = "https://api.github.com/repos/k3d-io/k3d/releases/assets/229450045" [tools.k3d."platforms.macos-arm64"] checksum = "sha256:8da468daa7dc7cf7cdd4735f90a9bb05179fa27858250f62e3d8cdf5b5ca0698" url = "https://github.com/k3d-io/k3d/releases/download/v5.8.3/k3d-darwin-arm64" +url_api = "https://api.github.com/repos/k3d-io/k3d/releases/assets/229450067" [[tools.kubectl]] version = "1.36.1" @@ -195,14 +255,17 @@ backend = "aqua:protocolbuffers/protobuf/protoc" [tools.protoc."platforms.linux-arm64"] checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79" url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip" +url_api = "https://api.github.com/repos/protocolbuffers/protobuf/releases/assets/350795076" [tools.protoc."platforms.linux-x64"] checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323" url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip" +url_api = "https://api.github.com/repos/protocolbuffers/protobuf/releases/assets/350795083" [tools.protoc."platforms.macos-arm64"] checksum = "sha256:b9576b5fa1a1ef3fe13a8c91d9d8204b46545759bea5ae155cd6ba2ea4cdaeed" url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-aarch_64.zip" +url_api = "https://api.github.com/repos/protocolbuffers/protobuf/releases/assets/350795082" [[tools.python]] version = "3.14.5" @@ -251,16 +314,19 @@ backend = "aqua:astral-sh/uv" [tools.uv."platforms.linux-arm64"] checksum = "sha256:55bd1c1c10ec8b95a8c184f5e18b566703c6ab105f0fc118aaa4d748aabf28e4" url = "https://github.com/astral-sh/uv/releases/download/0.10.12/uv-aarch64-unknown-linux-musl.tar.gz" +url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/377491942" provenance = "github-attestations" [tools.uv."platforms.linux-x64"] checksum = "sha256:adccf40b5d1939a5e0093081ec2307ea24235adf7c2d96b122c561fa37711c46" url = "https://github.com/astral-sh/uv/releases/download/0.10.12/uv-x86_64-unknown-linux-musl.tar.gz" +url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/377491998" provenance = "github-attestations" [tools.uv."platforms.macos-arm64"] checksum = "sha256:ae738b5661a900579ec621d3918c0ef17bdec0da2a8a6d8b161137cd15f25414" url = "https://github.com/astral-sh/uv/releases/download/0.10.12/uv-aarch64-apple-darwin.tar.gz" +url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/377491929" provenance = "github-attestations" [[tools.zig]] diff --git a/mise.toml b/mise.toml index da0c9cbfc..ac29a927b 100644 --- a/mise.toml +++ b/mise.toml @@ -25,6 +25,12 @@ node = "24.15.0" kubectl = "1.36.1" uv = "0.10.12" protoc = "29.6" +go = "1.26" +"go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint" = "2.12" +"go:google.golang.org/protobuf/cmd/protoc-gen-go" = "1.36.11" +"go:google.golang.org/grpc/cmd/protoc-gen-go-grpc" = "1.6.2" +"go:golang.org/x/tools/cmd/goimports" = "0.48.0" +buf = "1.72.0" helm = "4.2.0" helm-docs = "1.14.2" skaffold = "2.20.0" diff --git a/sdk/go/buf.gen.yaml b/sdk/go/buf.gen.yaml new file mode 100644 index 000000000..40e90d15d --- /dev/null +++ b/sdk/go/buf.gen.yaml @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Code generation for the Go SDK. The proto module boundary and validation +# policy live in the repo-level buf.yaml; this template only drives generation. +# buf compiles the module with its own compiler and runs protoc-gen-go / +# protoc-gen-go-grpc from mise-managed binaries. Limited to the client-surface +# closure (openshell, datamodel, sandbox, options); well-known types resolve +# through google.golang.org/protobuf and are not generated. +version: v2 + +inputs: + - directory: ../../proto + paths: + - ../../proto/openshell.proto + - ../../proto/datamodel.proto + - ../../proto/sandbox.proto + - ../../proto/options.proto + +plugins: + - local: protoc-gen-go + out: . + opt: + - module=github.com/NVIDIA/OpenShell/sdk/go + - Mopenshell.proto=github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1 + - Mdatamodel.proto=github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1 + - Msandbox.proto=github.com/NVIDIA/OpenShell/sdk/go/proto/sandboxv1 + - Moptions.proto=github.com/NVIDIA/OpenShell/sdk/go/proto/optionsv1 + - local: protoc-gen-go-grpc + out: . + opt: + - module=github.com/NVIDIA/OpenShell/sdk/go + - Mopenshell.proto=github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1 + - Mdatamodel.proto=github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1 + - Msandbox.proto=github.com/NVIDIA/OpenShell/sdk/go/proto/sandboxv1 + - Moptions.proto=github.com/NVIDIA/OpenShell/sdk/go/proto/optionsv1 diff --git a/sdk/go/go.mod b/sdk/go/go.mod new file mode 100644 index 000000000..4a7c16017 --- /dev/null +++ b/sdk/go/go.mod @@ -0,0 +1,22 @@ +module github.com/NVIDIA/OpenShell/sdk/go + +go 1.24.0 + +toolchain go1.26.4 + +require ( + github.com/stretchr/testify v1.11.1 + golang.org/x/oauth2 v0.35.0 + google.golang.org/grpc v1.80.0 + google.golang.org/protobuf v1.36.11 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.33.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/sdk/go/go.sum b/sdk/go/go.sum new file mode 100644 index 000000000..5b0f5d005 --- /dev/null +++ b/sdk/go/go.sum @@ -0,0 +1,50 @@ +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sdk/go/openshell/v1/auth.go b/sdk/go/openshell/v1/auth.go new file mode 100644 index 000000000..95a7838f0 --- /dev/null +++ b/sdk/go/openshell/v1/auth.go @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// AuthProvider supplies per-RPC credentials. It implements the +// grpc credentials.PerRPCCredentials interface. +type AuthProvider = types.AuthProvider + +type noAuth struct{} + +// NoAuth returns an AuthProvider that sends no credentials. +func NoAuth() AuthProvider { + return &noAuth{} +} + +func (n *noAuth) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { + return nil, nil +} + +func (n *noAuth) RequireTransportSecurity() bool { + return false +} + +type staticToken struct { + token string +} + +// StaticToken returns an AuthProvider that sends a fixed Bearer token. +func StaticToken(token string) AuthProvider { + return &staticToken{token: token} +} + +func (s *staticToken) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { + return map[string]string{ + "authorization": "Bearer " + s.token, + }, nil +} + +func (s *staticToken) RequireTransportSecurity() bool { + return true +} diff --git a/sdk/go/openshell/v1/auth_extra.go b/sdk/go/openshell/v1/auth_extra.go new file mode 100644 index 000000000..21a9e9e2c --- /dev/null +++ b/sdk/go/openshell/v1/auth_extra.go @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + "errors" + "maps" + "strings" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// extraHeadersAuth wraps a base AuthProvider with additional static headers +// that are merged into every GetRequestMetadata call. Extra headers take +// precedence over base headers on key collision (case-insensitive). +type extraHeadersAuth struct { + base types.AuthProvider + headers map[string]string // keys already lowercase, empty values filtered out +} + +// WithExtraHeaders wraps base with additional per-RPC headers. Keys are +// normalized to lowercase per HTTP/2 (RFC 9113). Empty-string values are +// silently dropped. The headers map is deep-copied at construction time, +// so later mutations to the caller's map have no effect. +// +// Returns an error if base is nil or if headers is nil, empty, or contains +// only empty-string values. +func WithExtraHeaders(base AuthProvider, headers map[string]string) (AuthProvider, error) { + if base == nil { + return nil, errors.New("base auth provider must not be nil") + } + if len(headers) == 0 { + return nil, errors.New("headers must not be nil or empty") + } + + // Deep-copy and normalize: lowercase keys, skip empty values. + normalized := make(map[string]string, len(headers)) + for k, v := range headers { + if v == "" { + continue + } + normalized[strings.ToLower(k)] = v + } + + if len(normalized) == 0 { + return nil, errors.New("headers must contain at least one non-empty value") + } + + return &extraHeadersAuth{ + base: base, + headers: normalized, + }, nil +} + +// GetRequestMetadata merges base metadata with extra headers. Extra headers +// win on key collision because they are applied after the base metadata. +func (e *extraHeadersAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + baseMD, err := e.base.GetRequestMetadata(ctx, uri...) + if err != nil { + return nil, err + } + + // Start with base metadata (may be nil for NoAuth). + // Normalize base keys to lowercase for case-insensitive collision. + merged := make(map[string]string, len(baseMD)+len(e.headers)) + for k, v := range baseMD { + merged[strings.ToLower(k)] = v + } + // Extra headers overwrite base on collision. + maps.Copy(merged, e.headers) + + return merged, nil +} + +// RequireTransportSecurity delegates to the base auth provider. +func (e *extraHeadersAuth) RequireTransportSecurity() bool { + return e.base.RequireTransportSecurity() +} diff --git a/sdk/go/openshell/v1/auth_extra_test.go b/sdk/go/openshell/v1/auth_extra_test.go new file mode 100644 index 000000000..09bd4d33d --- /dev/null +++ b/sdk/go/openshell/v1/auth_extra_test.go @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWithExtraHeaders_NilBase(t *testing.T) { + _, err := WithExtraHeaders(nil, map[string]string{"x-key": "val"}) + require.Error(t, err) + assert.Contains(t, err.Error(), "base") +} + +func TestWithExtraHeaders_NilHeaders(t *testing.T) { + _, err := WithExtraHeaders(NoAuth(), nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "headers") +} + +func TestWithExtraHeaders_EmptyHeaders(t *testing.T) { + _, err := WithExtraHeaders(NoAuth(), map[string]string{}) + require.Error(t, err) + assert.Contains(t, err.Error(), "headers") +} + +func TestWithExtraHeaders_AllEmptyValues(t *testing.T) { + // All values are empty strings, so after filtering, headers map is empty. + _, err := WithExtraHeaders(NoAuth(), map[string]string{"x-key": ""}) + require.Error(t, err) + assert.Contains(t, err.Error(), "headers") +} + +func TestWithExtraHeaders_MergesWithBase(t *testing.T) { + base := StaticToken("my-token") + auth, err := WithExtraHeaders(base, map[string]string{ + "x-proxy-key": "proxy-secret", + "x-tenant-id": "acme-corp", + }) + require.NoError(t, err) + + md, err := auth.GetRequestMetadata(context.Background()) + require.NoError(t, err) + + assert.Equal(t, "Bearer my-token", md["authorization"]) + assert.Equal(t, "proxy-secret", md["x-proxy-key"]) + assert.Equal(t, "acme-corp", md["x-tenant-id"]) +} + +func TestWithExtraHeaders_ExtraPrecedenceOnCollision(t *testing.T) { + base := StaticToken("my-token") + auth, err := WithExtraHeaders(base, map[string]string{ + "authorization": "Custom override-token", + }) + require.NoError(t, err) + + md, err := auth.GetRequestMetadata(context.Background()) + require.NoError(t, err) + + // Extra header wins over base. + assert.Equal(t, "Custom override-token", md["authorization"]) +} + +func TestWithExtraHeaders_CaseInsensitiveCollision(t *testing.T) { + base := StaticToken("my-token") + // "Authorization" with uppercase should still override "authorization". + auth, err := WithExtraHeaders(base, map[string]string{ + "Authorization": "Custom override-token", + }) + require.NoError(t, err) + + md, err := auth.GetRequestMetadata(context.Background()) + require.NoError(t, err) + + assert.Equal(t, "Custom override-token", md["authorization"]) +} + +func TestWithExtraHeaders_EmptyValueSkipped(t *testing.T) { + base := StaticToken("my-token") + auth, err := WithExtraHeaders(base, map[string]string{ + "x-proxy-key": "proxy-secret", + "x-empty": "", // Should be silently skipped. + }) + require.NoError(t, err) + + md, err := auth.GetRequestMetadata(context.Background()) + require.NoError(t, err) + + assert.Equal(t, "proxy-secret", md["x-proxy-key"]) + _, hasEmpty := md["x-empty"] + assert.False(t, hasEmpty, "empty-string header values should be skipped") +} + +func TestWithExtraHeaders_RequireTransportSecurity_Delegates(t *testing.T) { + tests := []struct { + name string + base AuthProvider + expected bool + }{ + { + name: "delegates to NoAuth (false)", + base: NoAuth(), + expected: false, + }, + { + name: "delegates to StaticToken (true)", + base: StaticToken("tok"), + expected: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + auth, err := WithExtraHeaders(tt.base, map[string]string{"x-key": "val"}) + require.NoError(t, err) + assert.Equal(t, tt.expected, auth.RequireTransportSecurity()) + }) + } +} + +func TestWithExtraHeaders_WithNoAuth(t *testing.T) { + auth, err := WithExtraHeaders(NoAuth(), map[string]string{ + "x-proxy-key": "proxy-secret", + }) + require.NoError(t, err) + + md, err := auth.GetRequestMetadata(context.Background()) + require.NoError(t, err) + + // NoAuth returns nil metadata, extra headers should still appear. + assert.Equal(t, "proxy-secret", md["x-proxy-key"]) +} + +func TestWithExtraHeaders_BaseError_Propagated(t *testing.T) { + base := &errAuth{err: errors.New("auth failure")} + auth, err := WithExtraHeaders(base, map[string]string{"x-key": "val"}) + require.NoError(t, err) + + _, err = auth.GetRequestMetadata(context.Background()) + require.Error(t, err) + assert.Contains(t, err.Error(), "auth failure") +} + +func TestWithExtraHeaders_DeepCopiesHeaders(t *testing.T) { + original := map[string]string{ + "x-key": "original-value", + } + auth, err := WithExtraHeaders(NoAuth(), original) + require.NoError(t, err) + + // Mutate the original map after construction. + original["x-key"] = "mutated-value" + + md, err := auth.GetRequestMetadata(context.Background()) + require.NoError(t, err) + + // The wrapper should use the value at construction time, not the mutated value. + assert.Equal(t, "original-value", md["x-key"]) +} + +// errAuth is a test helper that always returns an error. +type errAuth struct { + err error +} + +func (e *errAuth) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { + return nil, e.err +} + +func (e *errAuth) RequireTransportSecurity() bool { + return false +} diff --git a/sdk/go/openshell/v1/auth_refresh.go b/sdk/go/openshell/v1/auth_refresh.go new file mode 100644 index 000000000..a3cf8b583 --- /dev/null +++ b/sdk/go/openshell/v1/auth_refresh.go @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + "errors" + "sync" + "time" + + "golang.org/x/oauth2" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +const defaultLeeway = 10 * time.Second + +var errNilTokenSource = errors.New("openshell: TokenSource must not be nil") + +// RefreshOption configures the behavior of RefreshableToken. +type RefreshOption func(*refreshConfig) + +type refreshConfig struct { + leeway time.Duration + logger types.Logger +} + +func defaultRefreshConfig() refreshConfig { + return refreshConfig{ + leeway: defaultLeeway, + } +} + +// WithLeeway sets the duration before token expiry at which a proactive +// refresh is triggered. Default is 10 seconds. +func WithLeeway(d time.Duration) RefreshOption { + return func(c *refreshConfig) { + if d < 0 { + d = 0 + } + c.leeway = d + } +} + +// WithLogger sets the logger used for stale-token fallback warnings. +// When not set, warnings are silently dropped. +func WithLogger(l types.Logger) RefreshOption { + return func(c *refreshConfig) { + c.logger = l + } +} + +type refreshableAuth struct { + source oauth2.TokenSource + mu sync.RWMutex + tok *oauth2.Token + leeway time.Duration + logger types.Logger +} + +func (r *refreshableAuth) isTokenValid() bool { + if r.tok == nil { + return false + } + if r.tok.Expiry.IsZero() { + return true + } + return time.Now().Before(r.tok.Expiry.Add(-r.leeway)) +} + +func (r *refreshableAuth) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { + // Fast path: RLock, return cached token if valid. + r.mu.RLock() + if r.isTokenValid() { + tok := r.tok.AccessToken + r.mu.RUnlock() + return map[string]string{"authorization": "Bearer " + tok}, nil + } + r.mu.RUnlock() + + // Slow path: Lock, re-check, fetch if still stale. + r.mu.Lock() + defer r.mu.Unlock() + + if r.isTokenValid() { + return map[string]string{"authorization": "Bearer " + r.tok.AccessToken}, nil + } + + newTok, err := r.source.Token() + if err != nil { + if r.tok != nil { + if r.logger != nil { + r.logger.Error(err, "token refresh failed, using cached token") + } + return map[string]string{"authorization": "Bearer " + r.tok.AccessToken}, nil + } + return nil, err + } + + if newTok == nil { + if r.tok != nil { + if r.logger != nil { + r.logger.Error(errors.New("token source returned nil token"), "token refresh returned nil, using cached token") + } + return map[string]string{"authorization": "Bearer " + r.tok.AccessToken}, nil + } + return nil, errors.New("openshell: token source returned nil token") + } + + r.tok = newTok + return map[string]string{"authorization": "Bearer " + r.tok.AccessToken}, nil +} + +func (r *refreshableAuth) RequireTransportSecurity() bool { + return true +} + +// RefreshableToken returns an AuthProvider that caches tokens from src +// and refreshes them before expiry. Concurrent callers share a single +// refresh call (coalesced via RWMutex double-checked locking). +func RefreshableToken(src oauth2.TokenSource, opts ...RefreshOption) (AuthProvider, error) { + if src == nil { + return nil, errNilTokenSource + } + + cfg := defaultRefreshConfig() + for _, o := range opts { + o(&cfg) + } + + return &refreshableAuth{ + source: src, + leeway: cfg.leeway, + logger: cfg.logger, + }, nil +} diff --git a/sdk/go/openshell/v1/auth_refresh_test.go b/sdk/go/openshell/v1/auth_refresh_test.go new file mode 100644 index 000000000..451e8e63d --- /dev/null +++ b/sdk/go/openshell/v1/auth_refresh_test.go @@ -0,0 +1,373 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/oauth2" +) + +// mockTokenSource implements oauth2.TokenSource for testing. +type mockTokenSource struct { + mu sync.Mutex + tokenFunc func() (*oauth2.Token, error) + callCount int +} + +func (m *mockTokenSource) Token() (*oauth2.Token, error) { + m.mu.Lock() + m.callCount++ + m.mu.Unlock() + return m.tokenFunc() +} + +func (m *mockTokenSource) calls() int { + m.mu.Lock() + defer m.mu.Unlock() + return m.callCount +} + +// --- Phase 2 tests: constructor validation --- + +func TestRefreshableToken_NilSource(t *testing.T) { + _, err := RefreshableToken(nil) + require.Error(t, err) + assert.Equal(t, "openshell: TokenSource must not be nil", err.Error()) +} + +func TestRefreshableToken_ValidSource(t *testing.T) { + src := &mockTokenSource{ + tokenFunc: func() (*oauth2.Token, error) { + return &oauth2.Token{AccessToken: "tok", Expiry: time.Now().Add(time.Hour)}, nil + }, + } + provider, err := RefreshableToken(src) + require.NoError(t, err) + assert.NotNil(t, provider) +} + +// --- Phase 3 / US1 tests: automatic token refresh --- + +func TestGetRequestMetadata_FirstCallFetchesToken(t *testing.T) { + src := &mockTokenSource{ + tokenFunc: func() (*oauth2.Token, error) { + return &oauth2.Token{AccessToken: "fresh-token", Expiry: time.Now().Add(time.Hour)}, nil + }, + } + provider, err := RefreshableToken(src) + require.NoError(t, err) + + md, err := provider.GetRequestMetadata(context.Background()) + require.NoError(t, err) + assert.Equal(t, "Bearer fresh-token", md["authorization"]) + assert.Equal(t, 1, src.calls()) +} + +func TestGetRequestMetadata_CachedTokenNoExtraCall(t *testing.T) { + src := &mockTokenSource{ + tokenFunc: func() (*oauth2.Token, error) { + return &oauth2.Token{AccessToken: "cached-token", Expiry: time.Now().Add(time.Hour)}, nil + }, + } + provider, err := RefreshableToken(src) + require.NoError(t, err) + + _, err = provider.GetRequestMetadata(context.Background()) + require.NoError(t, err) + + md, err := provider.GetRequestMetadata(context.Background()) + require.NoError(t, err) + assert.Equal(t, "Bearer cached-token", md["authorization"]) + assert.Equal(t, 1, src.calls(), "second call should use cache, not invoke TokenSource") +} + +func TestGetRequestMetadata_RefreshesExpiredToken(t *testing.T) { + var callNum atomic.Int32 + src := &mockTokenSource{ + tokenFunc: func() (*oauth2.Token, error) { + n := callNum.Add(1) + if n == 1 { + return &oauth2.Token{AccessToken: "old", Expiry: time.Now().Add(-time.Minute)}, nil + } + return &oauth2.Token{AccessToken: "new", Expiry: time.Now().Add(time.Hour)}, nil + }, + } + provider, err := RefreshableToken(src, WithLeeway(0)) + require.NoError(t, err) + + // First call gets the expired token, which is immediately stale. + md, err := provider.GetRequestMetadata(context.Background()) + require.NoError(t, err) + assert.Equal(t, "Bearer old", md["authorization"]) + + // Second call should trigger a refresh since the cached token is expired. + md, err = provider.GetRequestMetadata(context.Background()) + require.NoError(t, err) + assert.Equal(t, "Bearer new", md["authorization"]) + assert.Equal(t, 2, src.calls()) +} + +func TestGetRequestMetadata_ConcurrentSingleFlight(t *testing.T) { + var fetchCount atomic.Int32 + src := &mockTokenSource{ + tokenFunc: func() (*oauth2.Token, error) { + fetchCount.Add(1) + time.Sleep(10 * time.Millisecond) // simulate slow token fetch + return &oauth2.Token{AccessToken: "shared-token", Expiry: time.Now().Add(time.Hour)}, nil + }, + } + provider, err := RefreshableToken(src) + require.NoError(t, err) + + const goroutines = 1000 + var wg sync.WaitGroup + wg.Add(goroutines) + results := make([]string, goroutines) + errs := make([]error, goroutines) + + for i := range goroutines { + go func(idx int) { + defer wg.Done() + md, e := provider.GetRequestMetadata(context.Background()) + errs[idx] = e + if md != nil { + results[idx] = md["authorization"] + } + }(i) + } + wg.Wait() + + for i := range goroutines { + require.NoError(t, errs[i], "goroutine %d failed", i) + assert.Equal(t, "Bearer shared-token", results[i], "goroutine %d got wrong token", i) + } + assert.Equal(t, int32(1), fetchCount.Load(), "expected exactly 1 TokenSource.Token() call, got %d", fetchCount.Load()) +} + +func TestRefreshableAuth_RequireTransportSecurity(t *testing.T) { + src := &mockTokenSource{ + tokenFunc: func() (*oauth2.Token, error) { + return &oauth2.Token{AccessToken: "t"}, nil + }, + } + provider, err := RefreshableToken(src) + require.NoError(t, err) + assert.True(t, provider.RequireTransportSecurity()) +} + +// --- Phase 4 / US2 tests: graceful degradation --- + +func TestGetRequestMetadata_RefreshFailureReturnsStaleCachedToken(t *testing.T) { + var callNum atomic.Int32 + src := &mockTokenSource{ + tokenFunc: func() (*oauth2.Token, error) { + n := callNum.Add(1) + if n == 1 { + return &oauth2.Token{AccessToken: "stale", Expiry: time.Now().Add(-time.Minute)}, nil + } + return nil, fmt.Errorf("idp unavailable") + }, + } + provider, err := RefreshableToken(src, WithLeeway(0)) + require.NoError(t, err) + + // First call succeeds but returns already-expired token. + _, err = provider.GetRequestMetadata(context.Background()) + require.NoError(t, err) + + // Second call: refresh fails, should return stale token. + md, err := provider.GetRequestMetadata(context.Background()) + require.NoError(t, err) + assert.Equal(t, "Bearer stale", md["authorization"]) +} + +func TestGetRequestMetadata_RefreshFailureLogsWarning(t *testing.T) { + var callNum atomic.Int32 + src := &mockTokenSource{ + tokenFunc: func() (*oauth2.Token, error) { + n := callNum.Add(1) + if n == 1 { + return &oauth2.Token{AccessToken: "stale", Expiry: time.Now().Add(-time.Minute)}, nil + } + return nil, fmt.Errorf("idp unavailable") + }, + } + + logger := &captureLogger{} + provider, err := RefreshableToken(src, WithLeeway(0), WithLogger(logger)) + require.NoError(t, err) + + _, _ = provider.GetRequestMetadata(context.Background()) + _, _ = provider.GetRequestMetadata(context.Background()) + + require.Len(t, logger.errors, 1) + assert.Contains(t, logger.errors[0].msg, "token refresh failed") +} + +func TestGetRequestMetadata_RefreshFailureNoCachedTokenReturnsError(t *testing.T) { + src := &mockTokenSource{ + tokenFunc: func() (*oauth2.Token, error) { + return nil, fmt.Errorf("idp unavailable") + }, + } + provider, err := RefreshableToken(src) + require.NoError(t, err) + + _, err = provider.GetRequestMetadata(context.Background()) + require.Error(t, err) + assert.Contains(t, err.Error(), "idp unavailable") +} + +func TestGetRequestMetadata_RefreshFailureNoLoggerNoPanic(t *testing.T) { + var callNum atomic.Int32 + src := &mockTokenSource{ + tokenFunc: func() (*oauth2.Token, error) { + n := callNum.Add(1) + if n == 1 { + return &oauth2.Token{AccessToken: "stale", Expiry: time.Now().Add(-time.Minute)}, nil + } + return nil, fmt.Errorf("idp unavailable") + }, + } + provider, err := RefreshableToken(src, WithLeeway(0)) + require.NoError(t, err) + + _, _ = provider.GetRequestMetadata(context.Background()) + + assert.NotPanics(t, func() { + _, _ = provider.GetRequestMetadata(context.Background()) + }) +} + +// --- Phase 5 / US3 tests: configurable leeway --- + +func TestGetRequestMetadata_DefaultLeewayTriggersRefresh(t *testing.T) { + var callNum atomic.Int32 + src := &mockTokenSource{ + tokenFunc: func() (*oauth2.Token, error) { + n := callNum.Add(1) + return &oauth2.Token{ + AccessToken: fmt.Sprintf("token-%d", n), + Expiry: time.Now().Add(5 * time.Second), // within default 10s leeway + }, nil + }, + } + provider, err := RefreshableToken(src) // default 10s leeway + require.NoError(t, err) + + _, err = provider.GetRequestMetadata(context.Background()) + require.NoError(t, err) + + // Token expires in 5s, which is within 10s leeway, so next call should refresh. + md, err := provider.GetRequestMetadata(context.Background()) + require.NoError(t, err) + assert.Equal(t, "Bearer token-2", md["authorization"]) + assert.Equal(t, 2, src.calls()) +} + +func TestGetRequestMetadata_CustomLeewayTriggersRefresh(t *testing.T) { + var callNum atomic.Int32 + src := &mockTokenSource{ + tokenFunc: func() (*oauth2.Token, error) { + n := callNum.Add(1) + return &oauth2.Token{ + AccessToken: fmt.Sprintf("token-%d", n), + Expiry: time.Now().Add(25 * time.Second), // within custom 30s leeway + }, nil + }, + } + provider, err := RefreshableToken(src, WithLeeway(30*time.Second)) + require.NoError(t, err) + + _, err = provider.GetRequestMetadata(context.Background()) + require.NoError(t, err) + + // Token expires in 25s, which is within 30s leeway, so next call should refresh. + md, err := provider.GetRequestMetadata(context.Background()) + require.NoError(t, err) + assert.Equal(t, "Bearer token-2", md["authorization"]) + assert.Equal(t, 2, src.calls()) +} + +func TestGetRequestMetadata_ZeroExpiryNeverRefreshes(t *testing.T) { + src := &mockTokenSource{ + tokenFunc: func() (*oauth2.Token, error) { + return &oauth2.Token{AccessToken: "forever-token"}, nil // zero Expiry + }, + } + provider, err := RefreshableToken(src) + require.NoError(t, err) + + _, err = provider.GetRequestMetadata(context.Background()) + require.NoError(t, err) + + // Call multiple times; should never refresh since expiry is zero. + for range 10 { + _, err = provider.GetRequestMetadata(context.Background()) + require.NoError(t, err) + } + assert.Equal(t, 1, src.calls(), "zero-expiry token should never be refreshed") +} + +// --- benchmarks --- + +func BenchmarkGetRequestMetadata_CachedToken(b *testing.B) { + src := &mockTokenSource{ + tokenFunc: func() (*oauth2.Token, error) { + return &oauth2.Token{AccessToken: "bench-token", Expiry: time.Now().Add(time.Hour)}, nil + }, + } + provider, err := RefreshableToken(src) + require.NoError(b, err) + + // Prime the cache. + _, err = provider.GetRequestMetadata(context.Background()) + require.NoError(b, err) + + b.ResetTimer() + b.ReportAllocs() + for range b.N { + _, _ = provider.GetRequestMetadata(context.Background()) + } +} + +// --- helpers --- + +type logEntry struct { + err error + msg string +} + +type captureLogger struct { + mu sync.Mutex + debugs []string + infos []string + errors []logEntry +} + +func (l *captureLogger) Debug(msg string, _ ...any) { + l.mu.Lock() + defer l.mu.Unlock() + l.debugs = append(l.debugs, msg) +} + +func (l *captureLogger) Info(msg string, _ ...any) { + l.mu.Lock() + defer l.mu.Unlock() + l.infos = append(l.infos, msg) +} + +func (l *captureLogger) Error(err error, msg string, _ ...any) { + l.mu.Lock() + defer l.mu.Unlock() + l.errors = append(l.errors, logEntry{err: err, msg: msg}) +} diff --git a/sdk/go/openshell/v1/auth_test.go b/sdk/go/openshell/v1/auth_test.go new file mode 100644 index 000000000..2981fd792 --- /dev/null +++ b/sdk/go/openshell/v1/auth_test.go @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNoAuth_GetRequestMetadata(t *testing.T) { + auth := NoAuth() + md, err := auth.GetRequestMetadata(context.Background()) + require.NoError(t, err) + assert.Empty(t, md) +} + +func TestNoAuth_RequireTransportSecurity(t *testing.T) { + auth := NoAuth() + assert.False(t, auth.RequireTransportSecurity()) +} + +func TestStaticToken_GetRequestMetadata(t *testing.T) { + auth := StaticToken("my-secret-token") + md, err := auth.GetRequestMetadata(context.Background()) + require.NoError(t, err) + assert.Equal(t, "Bearer my-secret-token", md["authorization"]) +} + +func TestStaticToken_RequireTransportSecurity(t *testing.T) { + auth := StaticToken("token") + assert.True(t, auth.RequireTransportSecurity()) +} diff --git a/sdk/go/openshell/v1/client.go b/sdk/go/openshell/v1/client.go new file mode 100644 index 000000000..85defcaaa --- /dev/null +++ b/sdk/go/openshell/v1/client.go @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "sync" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" + + internalgrpc "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/internal/grpc" + "google.golang.org/grpc" +) + +// Config holds all settings needed to create a Client. +type Config = types.Config + +// ClientInterface defines the top-level SDK surface. +type ClientInterface interface { + Sandboxes() SandboxInterface + Providers() ProviderInterface + Services() ServiceInterface + Exec() ExecInterface + Files() FileInterface + Health() HealthInterface + SSH() SSHInterface + TCP() TCPInterface + Config() ConfigInterface + Policy() PolicyInterface + Close() error +} + +// SandboxInterface is defined in sandbox.go + +// ProviderInterface is defined in provider.go + +// ExecInterface is defined in exec.go + +// FileInterface is defined in file.go + +// Client implements ClientInterface. It holds a gRPC connection and provides +// sub-client accessors following the Kubernetes client-go pattern. +type Client struct { + conn *grpc.ClientConn + config Config + + closeOnce sync.Once + closeErr error + + sandboxes SandboxInterface + providers ProviderInterface + services ServiceInterface + exec ExecInterface + files FileInterface + health HealthInterface + ssh SSHInterface + tcp TCPInterface + cfg ConfigInterface + policy PolicyInterface +} + +// NewClient creates a new SDK client connected to the given gateway. +func NewClient(cfg Config) (*Client, error) { + if cfg.Address == "" { + return nil, &StatusError{Code: ErrorInvalidArgument, Message: "address must not be empty"} + } + + if cfg.Auth == nil { + cfg.Auth = NoAuth() + } + + var tlsParams *internalgrpc.TLSParams + if cfg.TLS != nil { + tlsParams = &internalgrpc.TLSParams{ + CertFile: cfg.TLS.CertFile, + KeyFile: cfg.TLS.KeyFile, + CAFile: cfg.TLS.CAFile, + Insecure: cfg.TLS.Insecure, + } + } + + conn, err := internalgrpc.NewConnection(cfg.Address, tlsParams, cfg.Auth) + if err != nil { + return nil, err + } + + c := &Client{ + conn: conn, + config: cfg, + } + + c.sandboxes = newSandboxClient(conn) + c.providers = &stubProviders{} + c.services = &stubServices{} + c.exec = &stubExec{} + c.files = &stubFiles{} + c.health = &stubHealth{} + c.ssh = &stubSSH{} + c.tcp = &stubTCP{} + c.cfg = &stubConfig{} + c.policy = &stubPolicy{} + + return c, nil +} + +// Sandboxes returns the sandbox sub-client. +func (c *Client) Sandboxes() SandboxInterface { return c.sandboxes } + +// Providers returns the provider sub-client. +func (c *Client) Providers() ProviderInterface { return c.providers } + +// Services returns the service sub-client. +func (c *Client) Services() ServiceInterface { return c.services } + +// Exec returns the exec sub-client. +func (c *Client) Exec() ExecInterface { return c.exec } + +// Files returns the file sub-client. +func (c *Client) Files() FileInterface { return c.files } + +// Health returns the health sub-client. +func (c *Client) Health() HealthInterface { return c.health } + +// SSH returns the SSH session sub-client. +func (c *Client) SSH() SSHInterface { return c.ssh } + +// TCP returns the TCP port forwarding sub-client. +func (c *Client) TCP() TCPInterface { return c.tcp } + +// Config returns the configuration sub-client. +func (c *Client) Config() ConfigInterface { return c.cfg } + +// Policy returns the policy management sub-client. +func (c *Client) Policy() PolicyInterface { return c.policy } + +// Close closes the underlying gRPC connection. Safe to call multiple times. +func (c *Client) Close() error { + c.closeOnce.Do(func() { + c.closeErr = c.conn.Close() + }) + return c.closeErr +} diff --git a/sdk/go/openshell/v1/client_test.go b/sdk/go/openshell/v1/client_test.go new file mode 100644 index 000000000..7d6029b05 --- /dev/null +++ b/sdk/go/openshell/v1/client_test.go @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewClient_EmptyAddress(t *testing.T) { + _, err := NewClient(Config{Address: ""}) + require.Error(t, err) + assert.Contains(t, err.Error(), "address") +} + +func TestNewClient_ValidConfig(t *testing.T) { + client, err := NewClient(Config{ + Address: "localhost:50051", + Auth: NoAuth(), + TLS: &TLSConfig{Insecure: true}, + }) + require.NoError(t, err) + require.NotNil(t, client) + + assert.NotNil(t, client.Sandboxes()) + assert.NotNil(t, client.Providers()) + assert.NotNil(t, client.Exec()) + assert.NotNil(t, client.Files()) + assert.NotNil(t, client.Health()) + + err = client.Close() + assert.NoError(t, err) +} + +func TestClient_CloseIdempotent(t *testing.T) { + client, err := NewClient(Config{ + Address: "localhost:50051", + Auth: NoAuth(), + TLS: &TLSConfig{Insecure: true}, + }) + require.NoError(t, err) + + err = client.Close() + assert.NoError(t, err) + + err = client.Close() + assert.NoError(t, err) +} + +func TestNewClient_DefaultAuth(t *testing.T) { + client, err := NewClient(Config{ + Address: "localhost:50051", + TLS: &TLSConfig{Insecure: true}, + }) + require.NoError(t, err) + require.NotNil(t, client) + _ = client.Close() +} diff --git a/sdk/go/openshell/v1/config.go b/sdk/go/openshell/v1/config.go new file mode 100644 index 000000000..58efb9bf2 --- /dev/null +++ b/sdk/go/openshell/v1/config.go @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// SandboxConfig represents the full configuration state of a sandbox. +type SandboxConfig = types.SandboxConfig + +// GatewayConfig represents gateway-global settings. +type GatewayConfig = types.GatewayConfig + +// ConfigUpdate represents a configuration mutation request. +type ConfigUpdate = types.ConfigUpdate + +// ConfigUpdateResult holds the result of a configuration update operation. +type ConfigUpdateResult = types.ConfigUpdateResult + +// SettingValue is a typed setting value (string, bool, int64, or bytes). +type SettingValue = types.SettingValue + +// SettingValueType identifies which typed field of a SettingValue is active. +type SettingValueType = types.SettingValueType + +// EffectiveSetting is a setting value paired with its resolved scope. +type EffectiveSetting = types.EffectiveSetting + +// SettingScope indicates whether a setting is sandbox or global. +type SettingScope = types.SettingScope + +// PolicySource indicates the source of a policy payload. +type PolicySource = types.PolicySource + +// SettingValueType constants re-exported from types package. +const ( + SettingValueString = types.SettingValueString + SettingValueBool = types.SettingValueBool + SettingValueInt = types.SettingValueInt + SettingValueBytes = types.SettingValueBytes +) + +// SettingScope constants re-exported from types package. +const ( + SettingScopeUnspecified = types.SettingScopeUnspecified + SettingScopeSandbox = types.SettingScopeSandbox + SettingScopeGlobal = types.SettingScopeGlobal +) + +// PolicySource constants re-exported from types package. +const ( + PolicySourceUnspecified = types.PolicySourceUnspecified + PolicySourceSandbox = types.PolicySourceSandbox + PolicySourceGlobal = types.PolicySourceGlobal +) + +// ConfigInterface defines operations for reading and updating gateway and +// sandbox configuration. +type ConfigInterface interface { + // GetSandbox retrieves the full configuration state for a sandbox, + // including policy, effective settings, and revision metadata. + // The sandbox is identified by name; the SDK resolves it to an ID internally. + GetSandbox(ctx context.Context, workspace, sandboxName string) (*SandboxConfig, error) + + // GetGateway retrieves gateway-global settings. + GetGateway(ctx context.Context) (*GatewayConfig, error) + + // Update applies a configuration mutation. For sandbox-scoped updates, + // set ConfigUpdate.Name to the sandbox name. For global-scoped updates, + // set ConfigUpdate.Global to true. + Update(ctx context.Context, workspace string, update *ConfigUpdate) (*ConfigUpdateResult, error) +} diff --git a/sdk/go/openshell/v1/doc.go b/sdk/go/openshell/v1/doc.go new file mode 100644 index 000000000..dd78d8189 --- /dev/null +++ b/sdk/go/openshell/v1/doc.go @@ -0,0 +1,358 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Package v1 provides a Go SDK for interacting with OpenShell servers. +// +// The SDK follows the Kubernetes client-go sub-client pattern: a single Client +// provides typed accessors for each resource domain (Sandboxes, Providers, Exec, +// Files, Health, Services, SSH, TCP, Config). All operations accept a context.Context and return idiomatic +// Go types. Proto-generated types never appear in the public API. +// +// # Quick Start +// +// client, err := v1.NewClient(v1.Config{ +// Address: "gateway.example.com:443", +// Auth: v1.StaticToken("my-token"), +// }) +// if err != nil { +// log.Fatal(err) +// } +// defer client.Close() +// +// # Sandbox Lifecycle +// +// sandbox, err := client.Sandboxes().Create(ctx, "default", "my-sandbox", &v1.SandboxSpec{ +// Template: &v1.SandboxTemplate{Image: "python:3.12"}, +// Environment: map[string]string{"LANG": "en_US.UTF-8"}, +// }, nil) +// if err != nil { +// log.Fatal(err) +// } +// +// sandbox, err = client.Sandboxes().WaitReady(ctx, "default", sandbox.Name) +// if err != nil { +// log.Fatal(err) +// } +// +// # Command Execution (available in a future release) +// +// result, err := client.Exec().Run(ctx, "default", sandbox.Name, []string{"echo", "hello"}, v1.ExecOptions{}) +// if err != nil { +// log.Fatal(err) +// } +// fmt.Println(string(result.Stdout)) // "hello\n" +// +// # Error Handling +// +// _, err = client.Sandboxes().Get(ctx, "default", "missing") +// if v1.IsNotFound(err) { +// // handle not found +// } +// +// # Watching +// +// watcher, err := client.Sandboxes().Watch(ctx, "default", sandbox.Name) +// if err != nil { +// log.Fatal(err) +// } +// defer watcher.Stop() +// for event := range watcher.ResultChan() { +// fmt.Printf("%s: %s\n", event.Type, event.Object.Name) +// } +// +// # Watching with StopOnTerminal +// +// Use StopOnTerminal to auto-close the watcher when the sandbox reaches a +// terminal phase (Ready or Error): +// +// watcher, err := client.Sandboxes().Watch(ctx, "default", sandbox.Name, +// v1.WatchOptions{StopOnTerminal: true}, +// ) +// if err != nil { +// log.Fatal(err) +// } +// for event := range watcher.ResultChan() { +// fmt.Printf("phase: %s\n", event.Object.Status.Phase) +// } +// // channel closes automatically after Ready or Error +// +// # Service Exposure (available in a future release) +// +// Expose an HTTP service running inside a sandbox and retrieve its public URL: +// +// endpoint, err := client.Services().Expose(ctx, "default", "my-sandbox", "api", 8080, true) +// if err != nil { +// log.Fatal(err) +// } +// fmt.Printf("Service URL: %s\n", endpoint.URL) +// +// endpoints, err := client.Services().List(ctx, "default", "my-sandbox") +// if err != nil { +// log.Fatal(err) +// } +// for _, ep := range endpoints { +// fmt.Printf(" %s → port %d (URL: %s)\n", ep.ServiceName, ep.TargetPort, ep.URL) +// } +// +// # Provider Profiles (available in a future release) +// +// List available provider profiles and import new ones: +// +// profiles, err := client.Providers().Profiles().List(ctx, "default") +// if err != nil { +// log.Fatal(err) +// } +// for _, p := range profiles { +// fmt.Printf("%s (%s): %s\n", p.DisplayName, p.Category, p.Description) +// } +// +// result, err := client.Providers().Profiles().Import(ctx, "default", []v1.ProfileImportItem{ +// {Source: "openai-profile.yaml", Profile: v1.ProviderProfile{ +// DisplayName: "OpenAI", +// Category: v1.ProfileCategoryInference, +// }}, +// }) +// if err != nil { +// log.Fatal(err) +// } +// for _, d := range result.Diagnostics { +// fmt.Printf("[%s] %s: %s\n", d.Severity, d.Field, d.Message) +// } +// +// # Credential Refresh (available in a future release) +// +// Configure gateway-owned credential refresh for a provider: +// +// status, err := client.Providers().Refresh().Configure(ctx, "default", &v1.RefreshConfig{ +// Provider: "openai", +// CredentialKey: "api-key", +// Strategy: v1.RefreshStrategyOAuth2ClientCredentials, +// Material: map[string]string{"client_id": "xxx", "client_secret": "yyy"}, +// }) +// if err != nil { +// log.Fatal(err) +// } +// fmt.Printf("Refresh status: %s (next: %s)\n", status.Status, status.NextRefreshAt) +// +// # Token Refresh +// +// Use RefreshableToken for automatic OAuth2 token caching and refresh. +// Concurrent callers share a single refresh call: +// +// tokenSource := oauth2Config.TokenSource(ctx, initialToken) +// auth, err := v1.RefreshableToken(tokenSource, +// v1.WithLeeway(30*time.Second), +// ) +// if err != nil { +// log.Fatal(err) +// } +// client, err := v1.NewClient(v1.Config{ +// Address: "gateway.example.com:443", +// Auth: auth, +// }) +// if err != nil { +// log.Fatal(err) +// } +// defer client.Close() +// +// # Extra Headers +// +// Use WithExtraHeaders to attach additional per-RPC headers to any auth +// provider. This is useful for edge proxies, API gateways, or any middleware +// that requires custom headers alongside standard authentication: +// +// base := v1.StaticToken("my-token") +// auth, err := v1.WithExtraHeaders(base, map[string]string{ +// "x-proxy-key": "proxy-secret", +// "x-tenant-id": "acme-corp", +// }) +// if err != nil { +// log.Fatal(err) +// } +// client, err := v1.NewClient(v1.Config{ +// Address: "gateway.example.com:443", +// Auth: auth, +// }) +// if err != nil { +// log.Fatal(err) +// } +// defer client.Close() +// +// Keys are normalized to lowercase (per HTTP/2 RFC 9113). On key collision, +// extra headers take precedence over base auth headers. Empty-string values +// are silently dropped. WithExtraHeaders composes with any AuthProvider, +// including RefreshableToken: +// +// tokenSource := oauth2Config.TokenSource(ctx, initialToken) +// refreshAuth, err := v1.RefreshableToken(tokenSource) +// if err != nil { +// log.Fatal(err) +// } +// auth, err := v1.WithExtraHeaders(refreshAuth, map[string]string{ +// "x-proxy-key": "proxy-secret", +// }) +// +// # SSH Session Management (available in a future release) +// +// Create an SSH session for a sandbox and use the returned connection details. +// Note: CreateSession accepts a sandbox ID, not a name. For name-based access +// with automatic session cleanup, prefer SSH().Tunnel() instead. +// +// session, err := client.SSH().CreateSession(ctx, "default", sandbox.ID) +// if err != nil { +// log.Fatal(err) +// } +// fmt.Printf("SSH to %s:%d (scheme: %s)\n", +// session.GatewayHost, session.GatewayPort, session.GatewayScheme) +// fmt.Printf("Host key: %s\n", session.HostKeyFingerprint) +// // Use session.Token to authenticate the SSH connection. +// +// revoked, err := client.SSH().RevokeSession(ctx, "default", session.Token) +// if err != nil { +// log.Fatal(err) +// } +// fmt.Printf("Session revoked: %v\n", revoked) +// +// # TCP Port Forwarding (available in a future release) +// +// Forward a local connection to a port inside a sandbox: +// +// conn, err := client.TCP().Forward(ctx, "default", "my-sandbox", 5432) +// if err != nil { +// log.Fatal(err) +// } +// defer conn.Close() +// +// // conn implements io.ReadWriteCloser, use it like a net.Conn. +// _, err = conn.Write([]byte("PING\n")) +// if err != nil { +// log.Fatal(err) +// } +// buf := make([]byte, 1024) +// n, err := conn.Read(buf) +// if err != nil { +// log.Fatal(err) +// } +// fmt.Printf("Response: %s\n", buf[:n]) +// +// Use WithForwardServiceID to tag the forwarding session with a service +// identifier for audit logging: +// +// conn, err := client.TCP().Forward(ctx, "default", "my-sandbox", 5432, +// v1.WithForwardServiceID("billing-db"), +// ) +// +// # SSH Tunneling (available in a future release) +// +// Create an SSH tunnel to a sandbox port in a single call. Tunnel combines +// session creation, TCP forwarding with an SSH relay target, and automatic +// session cleanup into one operation: +// +// tunnel, err := client.SSH().Tunnel(ctx, "default", "my-sandbox", 22) +// if err != nil { +// log.Fatal(err) +// } +// defer tunnel.Close() +// +// // tunnel implements io.ReadWriteCloser. The underlying SSH session +// // is automatically revoked when Close is called. +// _, err = tunnel.Write([]byte("SSH-2.0-client\r\n")) +// if err != nil { +// log.Fatal(err) +// } +// buf := make([]byte, 256) +// n, err := tunnel.Read(buf) +// if err != nil { +// log.Fatal(err) +// } +// fmt.Printf("Server banner: %s\n", buf[:n]) +// +// Use WithTunnelServiceID to associate a service identifier with the tunnel: +// +// tunnel, err := client.SSH().Tunnel(ctx, "default", "my-sandbox", 22, +// v1.WithTunnelServiceID("dev-ssh"), +// ) +// +// # Sandbox Policy +// +// Set an initial security policy when creating a sandbox: +// +// sandbox, err := client.Sandboxes().Create(ctx, "default", "secure-sandbox", &v1.SandboxSpec{ +// Template: &v1.SandboxTemplate{Image: "python:3.12"}, +// Policy: &v1.SandboxPolicy{ +// Version: 1, +// Filesystem: &v1.FilesystemPolicy{ +// IncludeWorkdir: true, +// ReadOnly: []string{"/usr", "/lib"}, +// }, +// Process: &v1.ProcessPolicy{ +// RunAsUser: "sandbox", +// RunAsGroup: "sandbox", +// }, +// NetworkPolicies: map[string]v1.NetworkPolicyRule{ +// "allow-api": { +// Name: "allow-api", +// Endpoints: []v1.PolicyNetworkEndpoint{ +// {Host: "api.example.com", Port: 443, Protocol: "tcp"}, +// }, +// }, +// }, +// }, +// }, nil) +// +// Replace the full policy at runtime via configuration update (available in a future release): +// +// result, err := client.Config().Update(ctx, "default", &v1.ConfigUpdate{ +// Name: "secure-sandbox", +// Policy: &v1.SandboxPolicy{ +// Version: 2, +// NetworkPolicies: map[string]v1.NetworkPolicyRule{ +// "allow-all": {Name: "allow-all"}, +// }, +// }, +// }) +// +// Read a policy back from revision history (available in a future release): +// +// revisions, err := client.Policy().List(ctx, "default") +// if err != nil { +// log.Fatal(err) +// } +// for _, rev := range revisions { +// if rev.Policy != nil { +// fmt.Printf("v%d: %d network rules\n", rev.Version, len(rev.Policy.NetworkPolicies)) +// } +// } +// +// # Configuration Management (available in a future release) +// +// Read sandbox and gateway configuration, and update settings: +// +// sbCfg, err := client.Config().GetSandbox(ctx, "default", "my-sandbox") +// if err != nil { +// log.Fatal(err) +// } +// fmt.Printf("Config revision: %d\n", sbCfg.ConfigRevision) +// for name, setting := range sbCfg.Settings { +// fmt.Printf(" %s = %v (scope: %s)\n", name, setting.Value, setting.Scope) +// } +// +// gwCfg, err := client.Config().GetGateway(ctx) +// if err != nil { +// log.Fatal(err) +// } +// fmt.Printf("Gateway settings revision: %d\n", gwCfg.SettingsRevision) +// +// result, err := client.Config().Update(ctx, "default", &v1.ConfigUpdate{ +// Name: "my-sandbox", +// SettingKey: "max_tokens", +// SettingValue: &v1.SettingValue{ +// Type: v1.SettingValueInt, +// IntVal: 8192, +// }, +// }) +// if err != nil { +// log.Fatal(err) +// } +// fmt.Printf("New settings revision: %d\n", result.SettingsRevision) +package v1 diff --git a/sdk/go/openshell/v1/errors.go b/sdk/go/openshell/v1/errors.go new file mode 100644 index 000000000..0033ae977 --- /dev/null +++ b/sdk/go/openshell/v1/errors.go @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// ErrorCode classifies SDK errors by their gRPC origin. +type ErrorCode = types.ErrorCode + +// ErrorCode values for classifying gRPC errors. +const ( + ErrorNotFound = types.ErrorNotFound + ErrorAlreadyExists = types.ErrorAlreadyExists + ErrorUnavailable = types.ErrorUnavailable + ErrorPermissionDenied = types.ErrorPermissionDenied + ErrorInvalidArgument = types.ErrorInvalidArgument + ErrorDeadlineExceeded = types.ErrorDeadlineExceeded + ErrorCancelled = types.ErrorCancelled + ErrorInternal = types.ErrorInternal + ErrorUnimplemented = types.ErrorUnimplemented + ErrorConflict = types.ErrorConflict + ErrorUnauthenticated = types.ErrorUnauthenticated +) + +// StatusError is the typed error returned by all SDK operations. +type StatusError = types.StatusError + +// IsNotFound returns true if the error indicates a resource was not found. +func IsNotFound(err error) bool { return types.IsNotFound(err) } + +// IsAlreadyExists returns true if the error indicates a resource already exists. +func IsAlreadyExists(err error) bool { return types.IsAlreadyExists(err) } + +// IsUnavailable returns true if the error indicates the service is unavailable. +func IsUnavailable(err error) bool { return types.IsUnavailable(err) } + +// IsPermissionDenied returns true if the error indicates insufficient permissions. +func IsPermissionDenied(err error) bool { return types.IsPermissionDenied(err) } + +// IsInvalidArgument returns true if the error indicates an invalid argument. +func IsInvalidArgument(err error) bool { return types.IsInvalidArgument(err) } + +// IsDeadlineExceeded returns true if the error indicates a deadline was exceeded. +func IsDeadlineExceeded(err error) bool { return types.IsDeadlineExceeded(err) } + +// IsCancelled returns true if the error indicates the operation was cancelled. +func IsCancelled(err error) bool { return types.IsCancelled(err) } + +// IsUnimplemented returns true if the error indicates the operation is not implemented. +func IsUnimplemented(err error) bool { return types.IsUnimplemented(err) } + +// IsConflict returns true if the error indicates a conflict, such as +// optimistic concurrency or an invalid state transition. +func IsConflict(err error) bool { return types.IsConflict(err) } + +// IsUnauthenticated returns true if the error indicates missing or invalid credentials. +func IsUnauthenticated(err error) bool { return types.IsUnauthenticated(err) } diff --git a/sdk/go/openshell/v1/errors_test.go b/sdk/go/openshell/v1/errors_test.go new file mode 100644 index 000000000..acc15c84b --- /dev/null +++ b/sdk/go/openshell/v1/errors_test.go @@ -0,0 +1,127 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStatusError_Error(t *testing.T) { + err := &StatusError{ + Code: ErrorNotFound, + Message: "sandbox not found", + } + s := err.Error() + assert.Contains(t, s, "NotFound") + assert.Contains(t, s, "sandbox not found") +} + +func TestStatusError_ErrorWithCause(t *testing.T) { + cause := fmt.Errorf("underlying issue") + err := &StatusError{ + Code: ErrorInvalidArgument, + Message: "bad name", + Cause: cause, + } + s := err.Error() + assert.Contains(t, s, "InvalidArgument") + assert.Contains(t, s, "bad name") + assert.ErrorIs(t, err, cause) +} + +func TestIsNotFound(t *testing.T) { + err := &StatusError{Code: ErrorNotFound, Message: "not found"} + assert.True(t, IsNotFound(err)) + assert.False(t, IsAlreadyExists(err)) +} + +func TestIsAlreadyExists(t *testing.T) { + err := &StatusError{Code: ErrorAlreadyExists, Message: "exists"} + assert.True(t, IsAlreadyExists(err)) + assert.False(t, IsNotFound(err)) +} + +func TestIsUnavailable(t *testing.T) { + err := &StatusError{Code: ErrorUnavailable, Message: "down"} + assert.True(t, IsUnavailable(err)) +} + +func TestIsPermissionDenied(t *testing.T) { + err := &StatusError{Code: ErrorPermissionDenied, Message: "denied"} + assert.True(t, IsPermissionDenied(err)) +} + +func TestIsInvalidArgument(t *testing.T) { + err := &StatusError{Code: ErrorInvalidArgument, Message: "invalid"} + assert.True(t, IsInvalidArgument(err)) +} + +func TestIsDeadlineExceeded(t *testing.T) { + err := &StatusError{Code: ErrorDeadlineExceeded, Message: "timeout"} + assert.True(t, IsDeadlineExceeded(err)) +} + +func TestIsCancelled(t *testing.T) { + err := &StatusError{Code: ErrorCancelled, Message: "cancelled"} + assert.True(t, IsCancelled(err)) +} + +func TestIsConflict(t *testing.T) { + err := &StatusError{Code: ErrorConflict, Message: "version conflict"} + assert.True(t, IsConflict(err)) + assert.False(t, IsNotFound(err)) +} + +func TestIsHelpers_NonStatusError(t *testing.T) { + err := errors.New("plain error") + assert.False(t, IsNotFound(err)) + assert.False(t, IsAlreadyExists(err)) + assert.False(t, IsUnavailable(err)) + assert.False(t, IsPermissionDenied(err)) + assert.False(t, IsInvalidArgument(err)) + assert.False(t, IsDeadlineExceeded(err)) + assert.False(t, IsCancelled(err)) + assert.False(t, IsConflict(err)) +} + +func TestIsHelpers_NilError(t *testing.T) { + assert.False(t, IsNotFound(nil)) + assert.False(t, IsConflict(nil)) +} + +func TestStatusError_WrappedError(t *testing.T) { + inner := &StatusError{Code: ErrorNotFound, Message: "not found"} + wrapped := fmt.Errorf("operation failed: %w", inner) + assert.True(t, IsNotFound(wrapped)) + + var se *StatusError + require.True(t, errors.As(wrapped, &se)) + assert.Equal(t, ErrorNotFound, se.Code) +} + +func TestErrorCode_String(t *testing.T) { + tests := []struct { + code ErrorCode + want string + }{ + {ErrorNotFound, "NotFound"}, + {ErrorAlreadyExists, "AlreadyExists"}, + {ErrorUnavailable, "Unavailable"}, + {ErrorPermissionDenied, "PermissionDenied"}, + {ErrorInvalidArgument, "InvalidArgument"}, + {ErrorDeadlineExceeded, "DeadlineExceeded"}, + {ErrorCancelled, "Cancelled"}, + {ErrorInternal, "Internal"}, + {ErrorUnimplemented, "Unimplemented"}, + {ErrorConflict, "Conflict"}, + } + for _, tt := range tests { + assert.Equal(t, tt.want, tt.code.String()) + } +} diff --git a/sdk/go/openshell/v1/exec.go b/sdk/go/openshell/v1/exec.go new file mode 100644 index 000000000..217a1dc9b --- /dev/null +++ b/sdk/go/openshell/v1/exec.go @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// ExecResult holds the collected output of a completed command execution. +type ExecResult = types.ExecResult + +// ExecChunk represents a single chunk of output from a streaming command execution. +type ExecChunk = types.ExecChunk + +// ExecStream provides an iterator interface over streaming command output. +type ExecStream interface { + Next() (*ExecChunk, error) + ExitCode() (int, error) + Close() error +} + +// InteractiveSession provides bidirectional I/O for interactive command execution. +type InteractiveSession interface { + Read(p []byte) (int, error) + Write(p []byte) (int, error) + Resize(cols, rows uint32) error + ExitCode() (int, error) + Close() error +} + +// ExecInterface defines command execution operations on sandboxes. +// Methods accept a sandbox name and resolve it to an ID internally. +type ExecInterface interface { + Run(ctx context.Context, workspace, sandboxName string, command []string, opts ...ExecOptions) (*ExecResult, error) + Stream(ctx context.Context, workspace, sandboxName string, command []string, opts ...ExecOptions) (ExecStream, error) + Interactive(ctx context.Context, workspace, sandboxName string, command []string, cols, rows uint32, opts ...ExecOptions) (InteractiveSession, error) +} diff --git a/sdk/go/openshell/v1/file.go b/sdk/go/openshell/v1/file.go new file mode 100644 index 000000000..0893c9c6c --- /dev/null +++ b/sdk/go/openshell/v1/file.go @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import "context" + +// FileInterface defines file transfer operations on sandboxes. +// Methods accept a sandbox name and resolve it to an ID internally. +type FileInterface interface { + Upload(ctx context.Context, workspace, sandboxName string, localPath string, remotePath string) error + Download(ctx context.Context, workspace, sandboxName string, remotePath string, localPath string) error +} diff --git a/sdk/go/openshell/v1/grpc_errors.go b/sdk/go/openshell/v1/grpc_errors.go new file mode 100644 index 000000000..4c3135116 --- /dev/null +++ b/sdk/go/openshell/v1/grpc_errors.go @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Package v1 provides the OpenShell SDK client. +// gRPC error conversion is handled by the internal/converter package. +package v1 + +import "context" + +func contextError(err error) error { + if err == nil { + return nil + } + switch err { + case context.DeadlineExceeded: + return &StatusError{Code: ErrorDeadlineExceeded, Message: err.Error(), Cause: err} + case context.Canceled: + return &StatusError{Code: ErrorCancelled, Message: err.Error(), Cause: err} + default: + return &StatusError{Code: ErrorInternal, Message: err.Error(), Cause: err} + } +} diff --git a/sdk/go/openshell/v1/health.go b/sdk/go/openshell/v1/health.go new file mode 100644 index 000000000..c5e62eaa3 --- /dev/null +++ b/sdk/go/openshell/v1/health.go @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// HealthResult holds the result of a health check. +type HealthResult = types.HealthResult + +// HealthInterface defines health check operations. +type HealthInterface interface { + Check(ctx context.Context) (*HealthResult, error) +} diff --git a/sdk/go/openshell/v1/integration_test.go b/sdk/go/openshell/v1/integration_test.go new file mode 100644 index 000000000..9c8123052 --- /dev/null +++ b/sdk/go/openshell/v1/integration_test.go @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//go:build integration + +package v1 + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func gatewayAddress(t *testing.T) string { + t.Helper() + addr := os.Getenv("OPENSHELL_GATEWAY_ADDRESS") + if addr == "" { + t.Skip("OPENSHELL_GATEWAY_ADDRESS not set") + } + return addr +} + +func TestIntegration_HealthCheck(t *testing.T) { + addr := gatewayAddress(t) + + client, err := NewClient(Config{Address: addr}) + require.NoError(t, err) + defer client.Close() + + t.Skip("TODO: Health().Check() is a stub until PR B lands") + + _, err = client.Health().Check(context.Background()) + require.NoError(t, err) +} + +func TestIntegration_ProviderLifecycle(t *testing.T) { + addr := gatewayAddress(t) + + client, err := NewClient(Config{Address: addr}) + require.NoError(t, err) + defer client.Close() + + t.Skip("TODO: implement provider create/get/list/delete integration test") +} + +func TestIntegration_SandboxLifecycle(t *testing.T) { + addr := gatewayAddress(t) + + client, err := NewClient(Config{Address: addr}) + require.NoError(t, err) + defer client.Close() + + t.Skip("TODO: implement sandbox create/wait-ready/delete integration test") +} + +func TestIntegration_ExecRun(t *testing.T) { + addr := gatewayAddress(t) + + client, err := NewClient(Config{Address: addr}) + require.NoError(t, err) + defer client.Close() + + t.Skip("TODO: implement exec run integration test") +} + +func TestIntegration_FileTransfer(t *testing.T) { + addr := gatewayAddress(t) + + client, err := NewClient(Config{Address: addr}) + require.NoError(t, err) + defer client.Close() + + t.Skip("TODO: implement file upload/download integration test") +} diff --git a/sdk/go/openshell/v1/internal/converter/copy.go b/sdk/go/openshell/v1/internal/converter/copy.go new file mode 100644 index 000000000..9ab7f0f0e --- /dev/null +++ b/sdk/go/openshell/v1/internal/converter/copy.go @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package converter + +import "google.golang.org/protobuf/types/known/structpb" + +// CopyStringMap returns a shallow copy of a string-to-string map. +// Returns nil for nil input. +func CopyStringMap(m map[string]string) map[string]string { + if m == nil { + return nil + } + c := make(map[string]string, len(m)) + for k, v := range m { + c[k] = v + } + return c +} + +// CopyBoolPtr returns a copy of a *bool pointer. +// Returns nil for nil input. +func CopyBoolPtr(p *bool) *bool { + if p == nil { + return nil + } + v := *p + return &v +} + +// CopyStringSlice returns a copy of a string slice. +// Returns nil for nil input. +func CopyStringSlice(s []string) []string { + if s == nil { + return nil + } + c := make([]string, len(s)) + copy(c, s) + return c +} + +// CopyByteSlice returns a copy of a byte slice. +// Returns nil for nil input. +func CopyByteSlice(b []byte) []byte { + if b == nil { + return nil + } + c := make([]byte, len(b)) + copy(c, b) + return c +} + +func structToMap(s *structpb.Struct) map[string]any { + if s == nil { + return nil + } + return s.AsMap() +} + +func mapToStruct(m map[string]any) (*structpb.Struct, error) { + if m == nil { + return nil, nil + } + return structpb.NewStruct(m) +} diff --git a/sdk/go/openshell/v1/internal/converter/coverage_test.go b/sdk/go/openshell/v1/internal/converter/coverage_test.go new file mode 100644 index 000000000..38ed5f5ed --- /dev/null +++ b/sdk/go/openshell/v1/internal/converter/coverage_test.go @@ -0,0 +1,234 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package converter + +import ( + "testing" + + dm "github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1" + pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1" + sandboxpb "github.com/NVIDIA/OpenShell/sdk/go/proto/sandboxv1" + "google.golang.org/protobuf/reflect/protoreflect" +) + +// These tests use protobuf reflection to detect proto fields that the +// converter layer does not handle. When buf generates new fields from an +// updated .proto, the field name appears in the proto descriptor but not in +// the "handled" set below. +// +// Unhandled fields FAIL the test so that proto drift is caught immediately. +// If a field is intentionally deferred, add it to the "skipped" set with a +// justification comment. + +func TestConverterCoversAllProtoFields_SandboxSpec(t *testing.T) { + handled := fieldSet{ + "log_level": true, + "environment": true, + "template": true, + "policy": true, + "providers": true, + "resource_requirements": true, + } + + assertAllFieldsCovered(t, (&pb.SandboxSpec{}).ProtoReflect().Descriptor(), handled, nil) +} + +func TestConverterCoversAllProtoFields_SandboxTemplate(t *testing.T) { + handled := fieldSet{ + "image": true, + "runtime_class_name": true, + "agent_socket": true, + "labels": true, + "annotations": true, + "environment": true, + "resources": true, + "user_namespaces": true, + "driver_config": true, + } + + assertAllFieldsCovered(t, (&pb.SandboxTemplate{}).ProtoReflect().Descriptor(), handled, nil) +} + +func TestConverterCoversAllProtoFields_SandboxStatus(t *testing.T) { + handled := fieldSet{ + "sandbox_name": true, + "agent_pod": true, + "agent_fd": true, + "sandbox_fd": true, + "phase": true, + "conditions": true, + "current_policy_version": true, + } + + assertAllFieldsCovered(t, (&pb.SandboxStatus{}).ProtoReflect().Descriptor(), handled, nil) +} + +func TestConverterCoversAllProtoFields_SandboxCondition(t *testing.T) { + handled := fieldSet{ + "type": true, + "status": true, + "reason": true, + "message": true, + "last_transition_time": true, + } + + assertAllFieldsCovered(t, (&pb.SandboxCondition{}).ProtoReflect().Descriptor(), handled, nil) +} + +func TestConverterCoversAllProtoFields_SandboxPolicy(t *testing.T) { + handled := fieldSet{ + "version": true, + "filesystem": true, + "network_policies": true, + "process": true, + "landlock": true, + } + + skipped := fieldSet{ + // Middleware support is not yet exposed in the SDK domain model. + // Tracked in GitHub issue #36 for Drop D. + "network_middlewares": true, + } + + assertAllFieldsCovered(t, (&sandboxpb.SandboxPolicy{}).ProtoReflect().Descriptor(), handled, skipped) +} + +func TestConverterCoversAllProtoFields_NetworkEndpoint(t *testing.T) { + handled := fieldSet{ + "host": true, + "port": true, + "ports": true, + "protocol": true, + "tls": true, + "enforcement": true, + "access": true, + "rules": true, + "allowed_ips": true, + "deny_rules": true, + "allow_encoded_slash": true, + "persisted_queries": true, + "graphql_persisted_queries": true, + "graphql_max_body_bytes": true, + "path": true, + "websocket_credential_rewrite": true, + "request_body_credential_rewrite": true, + "advisor_proposed": true, + "credential_signing": true, + "signing_service": true, + "signing_region": true, + "json_rpc_max_body_bytes": true, + "mcp": true, + } + + assertAllFieldsCovered(t, (&sandboxpb.NetworkEndpoint{}).ProtoReflect().Descriptor(), handled, nil) +} + +func TestConverterCoversAllProtoFields_L7Allow(t *testing.T) { + handled := fieldSet{ + "method": true, + "path": true, + "command": true, + "query": true, + "operation_type": true, + "operation_name": true, + "fields": true, + "params": true, + } + + assertAllFieldsCovered(t, (&sandboxpb.L7Allow{}).ProtoReflect().Descriptor(), handled, nil) +} + +func TestConverterCoversAllProtoFields_L7DenyRule(t *testing.T) { + handled := fieldSet{ + "method": true, + "path": true, + "command": true, + "query": true, + "operation_type": true, + "operation_name": true, + "fields": true, + "params": true, + } + + assertAllFieldsCovered(t, (&sandboxpb.L7DenyRule{}).ProtoReflect().Descriptor(), handled, nil) +} + +func TestConverterCoversAllProtoFields_Provider(t *testing.T) { + handled := fieldSet{ + "metadata": true, + "type": true, + "credentials": true, + "config": true, + "credential_expires_at_ms": true, + "profile_workspace": true, + "credential_handles": true, + } + + assertAllFieldsCovered(t, (&dm.Provider{}).ProtoReflect().Descriptor(), handled, nil) +} + +func TestConverterCoversAllProtoFields_CredentialHandle(t *testing.T) { + handled := fieldSet{ + "driver": true, + "handle": true, + "metadata": true, + } + + assertAllFieldsCovered(t, (&dm.CredentialHandle{}).ProtoReflect().Descriptor(), handled, nil) +} + +func TestConverterCoversAllProtoFields_McpOptions(t *testing.T) { + handled := fieldSet{ + "strict_tool_names": true, + "allow_all_known_mcp_methods": true, + } + + assertAllFieldsCovered(t, (&sandboxpb.McpOptions{}).ProtoReflect().Descriptor(), handled, nil) +} + +// fieldSet tracks proto field names that the converter handles. +type fieldSet map[string]bool + +// assertAllFieldsCovered fails the test for proto fields not present in +// either handled or skipped. Stale entries in the handled set (fields +// removed from the proto) also fail. +func assertAllFieldsCovered( + t *testing.T, + desc protoreflect.MessageDescriptor, + handled fieldSet, + skipped fieldSet, +) { + t.Helper() + + fields := desc.Fields() + for i := 0; i < fields.Len(); i++ { + name := string(fields.Get(i).Name()) + if handled[name] || skipped[name] { + continue + } + t.Errorf( + "proto %s field %q is not handled by the converter and not explicitly skipped. "+ + "Add converter support in the appropriate FromProto/ToProto function, "+ + "or add it to the skipped set with a justification.", + desc.FullName(), name, + ) + } + + for name := range handled { + found := false + for i := 0; i < fields.Len(); i++ { + if string(fields.Get(i).Name()) == name { + found = true + break + } + } + if !found { + t.Errorf( + "handled field %q is listed for proto %s but does not exist in the descriptor. "+ + "The proto field may have been removed or renamed.", + name, desc.FullName(), + ) + } + } +} diff --git a/sdk/go/openshell/v1/internal/converter/errors.go b/sdk/go/openshell/v1/internal/converter/errors.go new file mode 100644 index 000000000..d589088e8 --- /dev/null +++ b/sdk/go/openshell/v1/internal/converter/errors.go @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Package converter maps between gRPC/proto types and SDK domain types. +package converter + +import ( + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var grpcToSDK = map[codes.Code]types.ErrorCode{ + codes.NotFound: types.ErrorNotFound, + codes.AlreadyExists: types.ErrorAlreadyExists, + codes.Unavailable: types.ErrorUnavailable, + codes.PermissionDenied: types.ErrorPermissionDenied, + codes.Unauthenticated: types.ErrorUnauthenticated, + codes.InvalidArgument: types.ErrorInvalidArgument, + codes.DeadlineExceeded: types.ErrorDeadlineExceeded, + codes.Canceled: types.ErrorCancelled, + codes.Internal: types.ErrorInternal, + codes.Unimplemented: types.ErrorUnimplemented, + codes.Aborted: types.ErrorConflict, + codes.FailedPrecondition: types.ErrorConflict, +} + +// FromGRPCError converts a gRPC error to a typed StatusError. +// Returns nil for nil errors and OK status. Non-gRPC errors pass through unchanged. +func FromGRPCError(err error) error { + if err == nil { + return nil + } + + st, ok := status.FromError(err) + if !ok { + return err + } + + if st.Code() == codes.OK { + return nil + } + + code, mapped := grpcToSDK[st.Code()] + if !mapped { + code = types.ErrorInternal + } + + return &types.StatusError{ + Code: code, + Message: st.Message(), + Cause: err, + } +} diff --git a/sdk/go/openshell/v1/internal/converter/errors_test.go b/sdk/go/openshell/v1/internal/converter/errors_test.go new file mode 100644 index 000000000..c7238eaae --- /dev/null +++ b/sdk/go/openshell/v1/internal/converter/errors_test.go @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package converter + +import ( + "testing" + + v1 "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestFromGRPCError_NotFound(t *testing.T) { + grpcErr := status.Error(codes.NotFound, "sandbox not found") + err := FromGRPCError(grpcErr) + require.Error(t, err) + assert.True(t, v1.IsNotFound(err)) +} + +func TestFromGRPCError_AlreadyExists(t *testing.T) { + grpcErr := status.Error(codes.AlreadyExists, "already exists") + err := FromGRPCError(grpcErr) + require.Error(t, err) + assert.True(t, v1.IsAlreadyExists(err)) +} + +func TestFromGRPCError_Unavailable(t *testing.T) { + grpcErr := status.Error(codes.Unavailable, "service down") + err := FromGRPCError(grpcErr) + require.Error(t, err) + assert.True(t, v1.IsUnavailable(err)) +} + +func TestFromGRPCError_PermissionDenied(t *testing.T) { + grpcErr := status.Error(codes.PermissionDenied, "denied") + err := FromGRPCError(grpcErr) + require.Error(t, err) + assert.True(t, v1.IsPermissionDenied(err)) +} + +func TestFromGRPCError_InvalidArgument(t *testing.T) { + grpcErr := status.Error(codes.InvalidArgument, "bad arg") + err := FromGRPCError(grpcErr) + require.Error(t, err) + assert.True(t, v1.IsInvalidArgument(err)) +} + +func TestFromGRPCError_DeadlineExceeded(t *testing.T) { + grpcErr := status.Error(codes.DeadlineExceeded, "timeout") + err := FromGRPCError(grpcErr) + require.Error(t, err) + assert.True(t, v1.IsDeadlineExceeded(err)) +} + +func TestFromGRPCError_Cancelled(t *testing.T) { + grpcErr := status.Error(codes.Canceled, "cancelled") + err := FromGRPCError(grpcErr) + require.Error(t, err) + assert.True(t, v1.IsCancelled(err)) +} + +func TestFromGRPCError_Internal(t *testing.T) { + grpcErr := status.Error(codes.Internal, "internal error") + err := FromGRPCError(grpcErr) + require.Error(t, err) + + var se *v1.StatusError + require.ErrorAs(t, err, &se) + assert.Equal(t, v1.ErrorInternal, se.Code) +} + +func TestFromGRPCError_Unimplemented(t *testing.T) { + grpcErr := status.Error(codes.Unimplemented, "not implemented") + err := FromGRPCError(grpcErr) + require.Error(t, err) + + var se *v1.StatusError + require.ErrorAs(t, err, &se) + assert.Equal(t, v1.ErrorUnimplemented, se.Code) +} + +func TestFromGRPCError_Aborted(t *testing.T) { + grpcErr := status.Error(codes.Aborted, "version conflict") + err := FromGRPCError(grpcErr) + require.Error(t, err) + assert.True(t, v1.IsConflict(err)) + + var se *v1.StatusError + require.ErrorAs(t, err, &se) + assert.Equal(t, v1.ErrorConflict, se.Code) + assert.Equal(t, "version conflict", se.Message) +} + +func TestFromGRPCError_UnmappedCode(t *testing.T) { + grpcErr := status.Error(codes.DataLoss, "data loss") + err := FromGRPCError(grpcErr) + require.Error(t, err) + + var se *v1.StatusError + require.ErrorAs(t, err, &se) + assert.Equal(t, v1.ErrorInternal, se.Code) +} + +func TestFromGRPCError_NilError(t *testing.T) { + err := FromGRPCError(nil) + assert.NoError(t, err) +} + +func TestFromGRPCError_NonGRPCError(t *testing.T) { + err := FromGRPCError(assert.AnError) + require.Error(t, err) + assert.Equal(t, assert.AnError, err) +} + +func TestFromGRPCError_OKStatus(t *testing.T) { + grpcErr := status.Error(codes.OK, "") + err := FromGRPCError(grpcErr) + assert.NoError(t, err) +} diff --git a/sdk/go/openshell/v1/internal/converter/log.go b/sdk/go/openshell/v1/internal/converter/log.go new file mode 100644 index 000000000..42f530fb1 --- /dev/null +++ b/sdk/go/openshell/v1/internal/converter/log.go @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package converter + +import ( + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" + pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1" +) + +// --- LogLine --- + +// LogLineFromProto converts a proto SandboxLogLine to an SDK LogLine. +func LogLineFromProto(l *pb.SandboxLogLine) *types.LogLine { + if l == nil { + return nil + } + return &types.LogLine{ + Timestamp: TimeFromMillis(l.GetTimestampMs()), + Level: l.GetLevel(), + Target: l.GetTarget(), + Message: l.GetMessage(), + Source: l.GetSource(), + Fields: CopyStringMap(l.GetFields()), + } +} + +// --- LogResult --- + +// LogResultFromProto converts a proto GetSandboxLogsResponse to an SDK LogResult. +func LogResultFromProto(r *pb.GetSandboxLogsResponse) *types.LogResult { + if r == nil { + return nil + } + result := &types.LogResult{ + BufferTotal: r.GetBufferTotal(), + } + if logs := r.GetLogs(); len(logs) > 0 { + result.Lines = make([]types.LogLine, 0, len(logs)) + for _, l := range logs { + if converted := LogLineFromProto(l); converted != nil { + result.Lines = append(result.Lines, *converted) + } + } + } + return result +} diff --git a/sdk/go/openshell/v1/internal/converter/log_test.go b/sdk/go/openshell/v1/internal/converter/log_test.go new file mode 100644 index 000000000..746239626 --- /dev/null +++ b/sdk/go/openshell/v1/internal/converter/log_test.go @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package converter + +import ( + "testing" + + pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// --- LogLine --- + +func TestLogLineFromProto(t *testing.T) { + proto := &pb.SandboxLogLine{ + SandboxId: "sbx-1", + TimestampMs: 1700000000000, + Level: "INFO", + Target: "network", + Message: "Connection established", + Source: "sandbox-agent", + Fields: map[string]string{ + "host": "api.example.com", + "port": "443", + }, + } + + line := LogLineFromProto(proto) + + require.NotNil(t, line) + assert.False(t, line.Timestamp.IsZero()) + assert.Equal(t, "INFO", line.Level) + assert.Equal(t, "network", line.Target) + assert.Equal(t, "Connection established", line.Message) + assert.Equal(t, "sandbox-agent", line.Source) + assert.Equal(t, "api.example.com", line.Fields["host"]) + assert.Equal(t, "443", line.Fields["port"]) +} + +func TestLogLineFromProto_Nil(t *testing.T) { + assert.Nil(t, LogLineFromProto(nil)) +} + +func TestLogLineDeepCopy(t *testing.T) { + proto := &pb.SandboxLogLine{ + TimestampMs: 1700000000000, + Level: "WARN", + Message: "test", + Fields: map[string]string{ + "key": "value", + }, + } + + line := LogLineFromProto(proto) + proto.Fields["key"] = "changed" + + assert.Equal(t, "value", line.Fields["key"]) +} + +// --- LogResult --- + +func TestLogResultFromProto(t *testing.T) { + proto := &pb.GetSandboxLogsResponse{ + Logs: []*pb.SandboxLogLine{ + {TimestampMs: 1700000000000, Level: "INFO", Message: "first"}, + {TimestampMs: 1700000001000, Level: "DEBUG", Message: "second"}, + }, + BufferTotal: 100, + } + + result := LogResultFromProto(proto) + + require.NotNil(t, result) + assert.Len(t, result.Lines, 2) + assert.Equal(t, "INFO", result.Lines[0].Level) + assert.Equal(t, "first", result.Lines[0].Message) + assert.Equal(t, "DEBUG", result.Lines[1].Level) + assert.Equal(t, "second", result.Lines[1].Message) + assert.Equal(t, uint32(100), result.BufferTotal) +} + +func TestLogResultFromProto_Nil(t *testing.T) { + assert.Nil(t, LogResultFromProto(nil)) +} + +func TestLogResultFromProto_EmptyLogs(t *testing.T) { + proto := &pb.GetSandboxLogsResponse{ + BufferTotal: 0, + } + + result := LogResultFromProto(proto) + require.NotNil(t, result) + assert.Empty(t, result.Lines) + assert.Equal(t, uint32(0), result.BufferTotal) +} diff --git a/sdk/go/openshell/v1/internal/converter/network_policy.go b/sdk/go/openshell/v1/internal/converter/network_policy.go new file mode 100644 index 000000000..3e3e4887d --- /dev/null +++ b/sdk/go/openshell/v1/internal/converter/network_policy.go @@ -0,0 +1,332 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package converter + +import ( + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" + sbv1 "github.com/NVIDIA/OpenShell/sdk/go/proto/sandboxv1" +) + +// --- NetworkPolicyRule --- + +// NetworkPolicyRuleFromProto converts a proto NetworkPolicyRule to an SDK NetworkPolicyRule. +func NetworkPolicyRuleFromProto(r *sbv1.NetworkPolicyRule) *types.NetworkPolicyRule { + if r == nil { + return nil + } + result := &types.NetworkPolicyRule{ + Name: r.GetName(), + } + if eps := r.GetEndpoints(); len(eps) > 0 { + result.Endpoints = make([]types.PolicyNetworkEndpoint, len(eps)) + for i, ep := range eps { + if ep != nil { + result.Endpoints[i] = policyNetworkEndpointFromProto(ep) + } + } + } + if bins := r.GetBinaries(); len(bins) > 0 { + result.Binaries = make([]types.PolicyNetworkBinary, len(bins)) + for i, b := range bins { + if b != nil { + result.Binaries[i] = types.PolicyNetworkBinary{Path: b.GetPath()} + } + } + } + return result +} + +// NetworkPolicyRuleToProto converts an SDK NetworkPolicyRule to a proto NetworkPolicyRule. +func NetworkPolicyRuleToProto(r *types.NetworkPolicyRule) *sbv1.NetworkPolicyRule { + if r == nil { + return nil + } + result := &sbv1.NetworkPolicyRule{ + Name: r.Name, + } + if len(r.Endpoints) > 0 { + result.Endpoints = make([]*sbv1.NetworkEndpoint, len(r.Endpoints)) + for i := range r.Endpoints { + result.Endpoints[i] = policyNetworkEndpointToProto(&r.Endpoints[i]) + } + } + if len(r.Binaries) > 0 { + result.Binaries = make([]*sbv1.NetworkBinary, len(r.Binaries)) + for i := range r.Binaries { + result.Binaries[i] = &sbv1.NetworkBinary{Path: r.Binaries[i].Path} + } + } + return result +} + +// --- PolicyNetworkEndpoint --- + +func policyNetworkEndpointFromProto(ep *sbv1.NetworkEndpoint) types.PolicyNetworkEndpoint { + result := types.PolicyNetworkEndpoint{ + Host: ep.GetHost(), + Port: ep.GetPort(), + Protocol: ep.GetProtocol(), + TLS: ep.GetTls(), + Enforcement: ep.GetEnforcement(), + Access: ep.GetAccess(), + AllowEncodedSlash: ep.GetAllowEncodedSlash(), + PersistedQueries: ep.GetPersistedQueries(), + GraphqlMaxBodyBytes: ep.GetGraphqlMaxBodyBytes(), + Path: ep.GetPath(), + WebsocketCredentialRewrite: ep.GetWebsocketCredentialRewrite(), + RequestBodyCredentialRewrite: ep.GetRequestBodyCredentialRewrite(), + AdvisorProposed: ep.GetAdvisorProposed(), + CredentialSigning: ep.GetCredentialSigning(), + SigningService: ep.GetSigningService(), + SigningRegion: ep.GetSigningRegion(), + JsonRpcMaxBodyBytes: ep.GetJsonRpcMaxBodyBytes(), + } + if mcp := ep.GetMcp(); mcp != nil { + result.Mcp = mcpOptionsFromProto(mcp) + } + if ports := ep.GetPorts(); len(ports) > 0 { + result.Ports = make([]uint32, len(ports)) + copy(result.Ports, ports) + } + if ips := ep.GetAllowedIps(); len(ips) > 0 { + result.AllowedIPs = CopyStringSlice(ips) + } + if rules := ep.GetRules(); len(rules) > 0 { + result.Rules = make([]types.L7Rule, len(rules)) + for i, r := range rules { + if r != nil { + result.Rules[i] = l7RuleFromProto(r) + } + } + } + if deny := ep.GetDenyRules(); len(deny) > 0 { + result.DenyRules = make([]types.L7DenyRule, len(deny)) + for i, r := range deny { + if r != nil { + result.DenyRules[i] = l7DenyRuleFromProto(r) + } + } + } + if gql := ep.GetGraphqlPersistedQueries(); len(gql) > 0 { + result.GraphqlPersistedQueries = make(map[string]types.GraphqlOperation, len(gql)) + for k, v := range gql { + if v != nil { + result.GraphqlPersistedQueries[k] = graphqlOperationFromProto(v) + } + } + } + return result +} + +func policyNetworkEndpointToProto(ep *types.PolicyNetworkEndpoint) *sbv1.NetworkEndpoint { + result := &sbv1.NetworkEndpoint{ + Host: ep.Host, + Port: ep.Port, + Protocol: ep.Protocol, + Tls: ep.TLS, + Enforcement: ep.Enforcement, + Access: ep.Access, + AllowEncodedSlash: ep.AllowEncodedSlash, + PersistedQueries: ep.PersistedQueries, + GraphqlMaxBodyBytes: ep.GraphqlMaxBodyBytes, + Path: ep.Path, + WebsocketCredentialRewrite: ep.WebsocketCredentialRewrite, + RequestBodyCredentialRewrite: ep.RequestBodyCredentialRewrite, + AdvisorProposed: ep.AdvisorProposed, + CredentialSigning: ep.CredentialSigning, + SigningService: ep.SigningService, + SigningRegion: ep.SigningRegion, + JsonRpcMaxBodyBytes: ep.JsonRpcMaxBodyBytes, + } + if ep.Mcp != nil { + result.Mcp = mcpOptionsToProto(ep.Mcp) + } + if len(ep.Ports) > 0 { + result.Ports = make([]uint32, len(ep.Ports)) + copy(result.Ports, ep.Ports) + } + if len(ep.AllowedIPs) > 0 { + result.AllowedIps = CopyStringSlice(ep.AllowedIPs) + } + if len(ep.Rules) > 0 { + result.Rules = make([]*sbv1.L7Rule, len(ep.Rules)) + for i := range ep.Rules { + result.Rules[i] = l7RuleToProto(&ep.Rules[i]) + } + } + if len(ep.DenyRules) > 0 { + result.DenyRules = make([]*sbv1.L7DenyRule, len(ep.DenyRules)) + for i := range ep.DenyRules { + result.DenyRules[i] = l7DenyRuleToProto(&ep.DenyRules[i]) + } + } + if len(ep.GraphqlPersistedQueries) > 0 { + result.GraphqlPersistedQueries = make(map[string]*sbv1.GraphqlOperation, len(ep.GraphqlPersistedQueries)) + for k, v := range ep.GraphqlPersistedQueries { + result.GraphqlPersistedQueries[k] = graphqlOperationToProto(&v) + } + } + return result +} + +// --- L7Rule --- + +func l7RuleFromProto(r *sbv1.L7Rule) types.L7Rule { + result := types.L7Rule{} + if a := r.GetAllow(); a != nil { + result.Allow = &types.L7Allow{ + Method: a.GetMethod(), + Path: a.GetPath(), + Command: a.GetCommand(), + OperationType: a.GetOperationType(), + OperationName: a.GetOperationName(), + Fields: CopyStringSlice(a.GetFields()), + } + if q := a.GetQuery(); len(q) > 0 { + result.Allow.Query = l7QueryMapFromProto(q) + } + if p := a.GetParams(); len(p) > 0 { + result.Allow.Params = l7QueryMapFromProto(p) + } + } + return result +} + +func l7RuleToProto(r *types.L7Rule) *sbv1.L7Rule { + result := &sbv1.L7Rule{} + if r.Allow != nil { + result.Allow = &sbv1.L7Allow{ + Method: r.Allow.Method, + Path: r.Allow.Path, + Command: r.Allow.Command, + OperationType: r.Allow.OperationType, + OperationName: r.Allow.OperationName, + Fields: CopyStringSlice(r.Allow.Fields), + } + if len(r.Allow.Query) > 0 { + result.Allow.Query = l7QueryMapToProto(r.Allow.Query) + } + if len(r.Allow.Params) > 0 { + result.Allow.Params = l7QueryMapToProto(r.Allow.Params) + } + } + return result +} + +// --- L7DenyRule --- + +func l7DenyRuleFromProto(r *sbv1.L7DenyRule) types.L7DenyRule { + result := types.L7DenyRule{ + Method: r.GetMethod(), + Path: r.GetPath(), + Command: r.GetCommand(), + OperationType: r.GetOperationType(), + OperationName: r.GetOperationName(), + Fields: CopyStringSlice(r.GetFields()), + Query: l7QueryMapFromProtoDeny(r.GetQuery()), + } + if p := r.GetParams(); len(p) > 0 { + result.Params = l7QueryMapFromProto(p) + } + return result +} + +func l7DenyRuleToProto(r *types.L7DenyRule) *sbv1.L7DenyRule { + result := &sbv1.L7DenyRule{ + Method: r.Method, + Path: r.Path, + Command: r.Command, + OperationType: r.OperationType, + OperationName: r.OperationName, + Fields: CopyStringSlice(r.Fields), + } + if len(r.Query) > 0 { + result.Query = l7QueryMapToProtoDeny(r.Query) + } + if len(r.Params) > 0 { + result.Params = l7QueryMapToProto(r.Params) + } + return result +} + +// --- L7QueryMatcher helpers --- + +func l7QueryMapFromProto(m map[string]*sbv1.L7QueryMatcher) map[string]types.L7QueryMatcher { + if len(m) == 0 { + return nil + } + result := make(map[string]types.L7QueryMatcher, len(m)) + for k, v := range m { + if v != nil { + result[k] = types.L7QueryMatcher{ + Glob: v.GetGlob(), + Any: CopyStringSlice(v.GetAny()), + } + } + } + return result +} + +func l7QueryMapToProto(m map[string]types.L7QueryMatcher) map[string]*sbv1.L7QueryMatcher { + if len(m) == 0 { + return nil + } + result := make(map[string]*sbv1.L7QueryMatcher, len(m)) + for k, v := range m { + result[k] = &sbv1.L7QueryMatcher{ + Glob: v.Glob, + Any: CopyStringSlice(v.Any), + } + } + return result +} + +// L7DenyRule uses the same L7QueryMatcher proto type but on a different message. +func l7QueryMapFromProtoDeny(m map[string]*sbv1.L7QueryMatcher) map[string]types.L7QueryMatcher { + return l7QueryMapFromProto(m) +} + +func l7QueryMapToProtoDeny(m map[string]types.L7QueryMatcher) map[string]*sbv1.L7QueryMatcher { + return l7QueryMapToProto(m) +} + +// --- GraphqlOperation --- + +func graphqlOperationFromProto(op *sbv1.GraphqlOperation) types.GraphqlOperation { + return types.GraphqlOperation{ + OperationType: op.GetOperationType(), + OperationName: op.GetOperationName(), + Fields: CopyStringSlice(op.GetFields()), + } +} + +func graphqlOperationToProto(op *types.GraphqlOperation) *sbv1.GraphqlOperation { + return &sbv1.GraphqlOperation{ + OperationType: op.OperationType, + OperationName: op.OperationName, + Fields: CopyStringSlice(op.Fields), + } +} + +// --- McpOptions --- + +func mcpOptionsFromProto(m *sbv1.McpOptions) *types.McpOptions { + if m == nil { + return nil + } + return &types.McpOptions{ + StrictToolNames: m.StrictToolNames, + AllowAllKnownMcpMethods: m.AllowAllKnownMcpMethods, + } +} + +func mcpOptionsToProto(m *types.McpOptions) *sbv1.McpOptions { + if m == nil { + return nil + } + return &sbv1.McpOptions{ + StrictToolNames: m.StrictToolNames, + AllowAllKnownMcpMethods: m.AllowAllKnownMcpMethods, + } +} diff --git a/sdk/go/openshell/v1/internal/converter/policy.go b/sdk/go/openshell/v1/internal/converter/policy.go new file mode 100644 index 000000000..780fadb56 --- /dev/null +++ b/sdk/go/openshell/v1/internal/converter/policy.go @@ -0,0 +1,304 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package converter + +import ( + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" + pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1" + sbv1 "github.com/NVIDIA/OpenShell/sdk/go/proto/sandboxv1" +) + +// --- PolicyLoadStatus enum mapping --- + +// PolicyLoadStatusFromProto converts a proto PolicyStatus to an SDK PolicyLoadStatus. +func PolicyLoadStatusFromProto(s pb.PolicyStatus) types.PolicyLoadStatus { + switch s { + case pb.PolicyStatus_POLICY_STATUS_PENDING: + return types.PolicyLoadStatusPending + case pb.PolicyStatus_POLICY_STATUS_LOADED: + return types.PolicyLoadStatusLoaded + case pb.PolicyStatus_POLICY_STATUS_FAILED: + return types.PolicyLoadStatusFailed + case pb.PolicyStatus_POLICY_STATUS_SUPERSEDED: + return types.PolicyLoadStatusSuperseded + default: + return types.PolicyLoadStatusUnspecified + } +} + +// PolicyLoadStatusToProto converts an SDK PolicyLoadStatus to a proto PolicyStatus. +func PolicyLoadStatusToProto(s types.PolicyLoadStatus) pb.PolicyStatus { + switch s { + case types.PolicyLoadStatusPending: + return pb.PolicyStatus_POLICY_STATUS_PENDING + case types.PolicyLoadStatusLoaded: + return pb.PolicyStatus_POLICY_STATUS_LOADED + case types.PolicyLoadStatusFailed: + return pb.PolicyStatus_POLICY_STATUS_FAILED + case types.PolicyLoadStatusSuperseded: + return pb.PolicyStatus_POLICY_STATUS_SUPERSEDED + default: + return pb.PolicyStatus_POLICY_STATUS_UNSPECIFIED + } +} + +// --- PolicyChunk --- + +// PolicyChunkFromProto converts a proto PolicyChunk to an SDK PolicyChunk. +func PolicyChunkFromProto(c *pb.PolicyChunk) *types.PolicyChunk { + if c == nil { + return nil + } + return &types.PolicyChunk{ + ID: c.GetId(), + Status: c.GetStatus(), + RuleName: c.GetRuleName(), + ProposedRule: NetworkPolicyRuleFromProto(c.GetProposedRule()), + Rationale: c.GetRationale(), + SecurityNotes: c.GetSecurityNotes(), + Confidence: c.GetConfidence(), + DenialSummaryIDs: CopyStringSlice(c.GetDenialSummaryIds()), + CreatedAt: TimeFromMillis(c.GetCreatedAtMs()), + DecidedAt: TimeFromMillis(c.GetDecidedAtMs()), + Stage: c.GetStage(), + SupersedesChunkID: c.GetSupersedesChunkId(), + HitCount: c.GetHitCount(), + FirstSeen: TimeFromMillis(c.GetFirstSeenMs()), + LastSeen: TimeFromMillis(c.GetLastSeenMs()), + Binary: c.GetBinary(), + ValidationResult: c.GetValidationResult(), + RejectionReason: c.GetRejectionReason(), + } +} + +// --- DraftPolicy --- + +// DraftPolicyFromProto converts a proto GetDraftPolicyResponse to an SDK DraftPolicy. +func DraftPolicyFromProto(r *pb.GetDraftPolicyResponse) *types.DraftPolicy { + if r == nil { + return nil + } + result := &types.DraftPolicy{ + RollingSummary: r.GetRollingSummary(), + DraftVersion: r.GetDraftVersion(), + LastAnalyzedAt: TimeFromMillis(r.GetLastAnalyzedAtMs()), + } + if chunks := r.GetChunks(); len(chunks) > 0 { + result.Chunks = make([]types.PolicyChunk, 0, len(chunks)) + for _, c := range chunks { + if converted := PolicyChunkFromProto(c); converted != nil { + result.Chunks = append(result.Chunks, *converted) + } + } + } + return result +} + +// --- SandboxPolicy --- + +// SandboxPolicyFromProto converts a proto SandboxPolicy to an SDK SandboxPolicy. +// Returns nil for nil input. All slice and map fields are deep-copied. +func SandboxPolicyFromProto(p *sbv1.SandboxPolicy) *types.SandboxPolicy { + if p == nil { + return nil + } + result := &types.SandboxPolicy{ + Version: p.GetVersion(), + Filesystem: filesystemPolicyFromProto(p.GetFilesystem()), + Landlock: landlockPolicyFromProto(p.GetLandlock()), + Process: processPolicyFromProto(p.GetProcess()), + } + if np := p.GetNetworkPolicies(); np != nil { + result.NetworkPolicies = make(map[string]types.NetworkPolicyRule, len(np)) + for k, v := range np { + if converted := NetworkPolicyRuleFromProto(v); converted != nil { + result.NetworkPolicies[k] = *converted + } + } + } + return result +} + +// SandboxPolicyToProto converts an SDK SandboxPolicy to a proto SandboxPolicy. +// Returns nil for nil input. All slice and map fields are deep-copied. +func SandboxPolicyToProto(p *types.SandboxPolicy) *sbv1.SandboxPolicy { + if p == nil { + return nil + } + result := &sbv1.SandboxPolicy{ + Version: p.Version, + Filesystem: filesystemPolicyToProto(p.Filesystem), + Landlock: landlockPolicyToProto(p.Landlock), + Process: processPolicyToProto(p.Process), + } + if p.NetworkPolicies != nil { + result.NetworkPolicies = make(map[string]*sbv1.NetworkPolicyRule, len(p.NetworkPolicies)) + for k, v := range p.NetworkPolicies { + result.NetworkPolicies[k] = NetworkPolicyRuleToProto(&v) + } + } + return result +} + +func filesystemPolicyFromProto(f *sbv1.FilesystemPolicy) *types.FilesystemPolicy { + if f == nil { + return nil + } + return &types.FilesystemPolicy{ + IncludeWorkdir: f.GetIncludeWorkdir(), + ReadOnly: CopyStringSlice(f.GetReadOnly()), + ReadWrite: CopyStringSlice(f.GetReadWrite()), + } +} + +func filesystemPolicyToProto(f *types.FilesystemPolicy) *sbv1.FilesystemPolicy { + if f == nil { + return nil + } + return &sbv1.FilesystemPolicy{ + IncludeWorkdir: f.IncludeWorkdir, + ReadOnly: CopyStringSlice(f.ReadOnly), + ReadWrite: CopyStringSlice(f.ReadWrite), + } +} + +func landlockPolicyFromProto(l *sbv1.LandlockPolicy) *types.LandlockPolicy { + if l == nil { + return nil + } + return &types.LandlockPolicy{ + Compatibility: l.GetCompatibility(), + } +} + +func landlockPolicyToProto(l *types.LandlockPolicy) *sbv1.LandlockPolicy { + if l == nil { + return nil + } + return &sbv1.LandlockPolicy{ + Compatibility: l.Compatibility, + } +} + +func processPolicyFromProto(p *sbv1.ProcessPolicy) *types.ProcessPolicy { + if p == nil { + return nil + } + return &types.ProcessPolicy{ + RunAsUser: p.GetRunAsUser(), + RunAsGroup: p.GetRunAsGroup(), + } +} + +func processPolicyToProto(p *types.ProcessPolicy) *sbv1.ProcessPolicy { + if p == nil { + return nil + } + return &sbv1.ProcessPolicy{ + RunAsUser: p.RunAsUser, + RunAsGroup: p.RunAsGroup, + } +} + +// --- SandboxPolicyRevision --- + +// SandboxPolicyRevisionFromProto converts a proto SandboxPolicyRevision to an SDK SandboxPolicyRevision. +func SandboxPolicyRevisionFromProto(r *pb.SandboxPolicyRevision) *types.SandboxPolicyRevision { + if r == nil { + return nil + } + return &types.SandboxPolicyRevision{ + Version: r.GetVersion(), + PolicyHash: r.GetPolicyHash(), + Status: PolicyLoadStatusFromProto(r.GetStatus()), + LoadError: r.GetLoadError(), + CreatedAt: TimeFromMillis(r.GetCreatedAtMs()), + LoadedAt: TimeFromMillis(r.GetLoadedAtMs()), + Policy: SandboxPolicyFromProto(r.GetPolicy()), + } +} + +// --- PolicyStatusResult --- + +// PolicyStatusResultFromProto converts a proto GetSandboxPolicyStatusResponse to an SDK PolicyStatusResult. +func PolicyStatusResultFromProto(r *pb.GetSandboxPolicyStatusResponse) *types.PolicyStatusResult { + if r == nil { + return nil + } + result := &types.PolicyStatusResult{ + ActiveVersion: r.GetActiveVersion(), + } + if rev := SandboxPolicyRevisionFromProto(r.GetRevision()); rev != nil { + result.Revision = *rev + } + return result +} + +// --- ApproveResult --- + +// ApproveResultFromProto converts a proto ApproveDraftChunkResponse to an SDK ApproveResult. +func ApproveResultFromProto(r *pb.ApproveDraftChunkResponse) *types.ApproveResult { + if r == nil { + return nil + } + return &types.ApproveResult{ + PolicyVersion: r.GetPolicyVersion(), + PolicyHash: r.GetPolicyHash(), + } +} + +// --- ApproveAllResult --- + +// ApproveAllResultFromProto converts a proto ApproveAllDraftChunksResponse to an SDK ApproveAllResult. +func ApproveAllResultFromProto(r *pb.ApproveAllDraftChunksResponse) *types.ApproveAllResult { + if r == nil { + return nil + } + return &types.ApproveAllResult{ + PolicyVersion: r.GetPolicyVersion(), + PolicyHash: r.GetPolicyHash(), + ChunksApproved: r.GetChunksApproved(), + ChunksSkipped: r.GetChunksSkipped(), + } +} + +// --- UndoResult --- + +// UndoResultFromProto converts a proto UndoDraftChunkResponse to an SDK UndoResult. +func UndoResultFromProto(r *pb.UndoDraftChunkResponse) *types.UndoResult { + if r == nil { + return nil + } + return &types.UndoResult{ + PolicyVersion: r.GetPolicyVersion(), + PolicyHash: r.GetPolicyHash(), + } +} + +// --- ClearResult --- + +// ClearResultFromProto converts a proto ClearDraftChunksResponse to an SDK ClearResult. +func ClearResultFromProto(r *pb.ClearDraftChunksResponse) *types.ClearResult { + if r == nil { + return nil + } + return &types.ClearResult{ + ChunksCleared: r.GetChunksCleared(), + } +} + +// --- DraftHistoryEntry --- + +// DraftHistoryEntryFromProto converts a proto DraftHistoryEntry to an SDK DraftHistoryEntry. +func DraftHistoryEntryFromProto(e *pb.DraftHistoryEntry) *types.DraftHistoryEntry { + if e == nil { + return nil + } + return &types.DraftHistoryEntry{ + Timestamp: TimeFromMillis(e.GetTimestampMs()), + EventType: e.GetEventType(), + Description: e.GetDescription(), + ChunkID: e.GetChunkId(), + } +} diff --git a/sdk/go/openshell/v1/internal/converter/provider.go b/sdk/go/openshell/v1/internal/converter/provider.go new file mode 100644 index 000000000..42799feab --- /dev/null +++ b/sdk/go/openshell/v1/internal/converter/provider.go @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package converter + +import ( + "time" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" + dm "github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1" +) + +// ProviderFromProto converts a proto Provider to an SDK Provider. +func ProviderFromProto(p *dm.Provider) *types.Provider { + if p == nil { + return nil + } + + result := &types.Provider{ + Type: p.GetType(), + Spec: types.ProviderSpec{ + Config: CopyStringMap(p.GetConfig()), + ProfileWorkspace: p.GetProfileWorkspace(), + }, + } + + if m := p.GetMetadata(); m != nil { + result.ID = m.GetId() + result.Name = m.GetName() + result.CreatedAt = TimeFromMillis(m.GetCreatedAtMs()) + result.Labels = CopyStringMap(m.GetLabels()) + result.Annotations = CopyStringMap(m.GetAnnotations()) + result.ResourceVersion = m.GetResourceVersion() + result.Workspace = m.GetWorkspace() + result.DeletionTimestamp = TimeFromMillisPtr(m.GetDeletionTimestampMs()) + } + + if expires := p.GetCredentialExpiresAtMs(); len(expires) > 0 { + result.Spec.CredentialExpiresAt = make(map[string]time.Time, len(expires)) + for k, ms := range expires { + result.Spec.CredentialExpiresAt[k] = TimeFromMillis(ms) + } + } + + if handles := p.GetCredentialHandles(); len(handles) > 0 { + result.Spec.CredentialHandles = make(map[string]types.CredentialHandle, len(handles)) + for k, h := range handles { + result.Spec.CredentialHandles[k] = types.CredentialHandle{ + Driver: h.GetDriver(), + Handle: h.GetHandle(), + Metadata: CopyStringMap(h.GetMetadata()), + } + } + } + + return result +} + +// ProviderToProto converts an SDK Provider to a proto Provider. +func ProviderToProto(p *types.Provider) *dm.Provider { + if p == nil { + return nil + } + + result := &dm.Provider{ + Metadata: &dm.ObjectMeta{ + Id: p.ID, + Name: p.Name, + CreatedAtMs: MillisFromTime(p.CreatedAt), + Labels: CopyStringMap(p.Labels), + Annotations: CopyStringMap(p.Annotations), + ResourceVersion: p.ResourceVersion, + Workspace: p.Workspace, + DeletionTimestampMs: MillisFromTimePtr(p.DeletionTimestamp), + }, + Type: p.Type, + Credentials: CopyStringMap(p.Spec.Credentials), + Config: CopyStringMap(p.Spec.Config), + ProfileWorkspace: p.Spec.ProfileWorkspace, + } + + if len(p.Spec.CredentialExpiresAt) > 0 { + result.CredentialExpiresAtMs = make(map[string]int64, len(p.Spec.CredentialExpiresAt)) + for k, t := range p.Spec.CredentialExpiresAt { + result.CredentialExpiresAtMs[k] = MillisFromTime(t) + } + } + + if len(p.Spec.CredentialHandles) > 0 { + result.CredentialHandles = make(map[string]*dm.CredentialHandle, len(p.Spec.CredentialHandles)) + for k, h := range p.Spec.CredentialHandles { + result.CredentialHandles[k] = &dm.CredentialHandle{ + Driver: h.Driver, + Handle: h.Handle, + Metadata: CopyStringMap(h.Metadata), + } + } + } + + return result +} diff --git a/sdk/go/openshell/v1/internal/converter/provider_test.go b/sdk/go/openshell/v1/internal/converter/provider_test.go new file mode 100644 index 000000000..dcead666a --- /dev/null +++ b/sdk/go/openshell/v1/internal/converter/provider_test.go @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package converter + +import ( + "testing" + "time" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" + dm "github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestProviderFromProto_Nil(t *testing.T) { + assert.Nil(t, ProviderFromProto(nil)) +} + +func TestProviderFromProto_Full(t *testing.T) { + proto := &dm.Provider{ + Metadata: &dm.ObjectMeta{ + Id: "prov-1", + Name: "claude-provider", + CreatedAtMs: 1700000000000, + Labels: map[string]string{"env": "prod"}, + Annotations: map[string]string{"note": "test"}, + ResourceVersion: 42, + Workspace: "default", + }, + Type: "claude", + Credentials: map[string]string{"api_key": "secret"}, + Config: map[string]string{"base_url": "https://api.example.com"}, + ProfileWorkspace: "shared", + CredentialExpiresAtMs: map[string]int64{ + "api_key": 1700003600000, + }, + CredentialHandles: map[string]*dm.CredentialHandle{ + "api_key": { + Driver: "vault", + Handle: "secret/data/claude", + Metadata: map[string]string{"version": "3"}, + }, + }, + } + + result := ProviderFromProto(proto) + + require.NotNil(t, result) + assert.Equal(t, "prov-1", result.ID) + assert.Equal(t, "claude-provider", result.Name) + assert.Equal(t, "claude", result.Type) + assert.Equal(t, uint64(42), result.ResourceVersion) + assert.Equal(t, "default", result.Workspace) + assert.Equal(t, map[string]string{"env": "prod"}, result.Labels) + assert.Equal(t, map[string]string{"note": "test"}, result.Annotations) + assert.Equal(t, map[string]string{"base_url": "https://api.example.com"}, result.Spec.Config) + assert.Equal(t, "shared", result.Spec.ProfileWorkspace) + + require.Len(t, result.Spec.CredentialExpiresAt, 1) + assert.False(t, result.Spec.CredentialExpiresAt["api_key"].IsZero()) + + require.Len(t, result.Spec.CredentialHandles, 1) + h := result.Spec.CredentialHandles["api_key"] + assert.Equal(t, "vault", h.Driver) + assert.Equal(t, "secret/data/claude", h.Handle) + assert.Equal(t, map[string]string{"version": "3"}, h.Metadata) +} + +func TestProviderFromProto_NilMetadata(t *testing.T) { + proto := &dm.Provider{ + Type: "openai", + Config: map[string]string{"key": "val"}, + } + + result := ProviderFromProto(proto) + + require.NotNil(t, result) + assert.Equal(t, "", result.ID) + assert.Equal(t, "", result.Name) + assert.Equal(t, "openai", result.Type) + assert.Equal(t, map[string]string{"key": "val"}, result.Spec.Config) +} + +func TestProviderFromProto_EmptyHandles(t *testing.T) { + proto := &dm.Provider{ + Type: "test", + CredentialHandles: map[string]*dm.CredentialHandle{}, + } + + result := ProviderFromProto(proto) + + require.NotNil(t, result) + assert.Nil(t, result.Spec.CredentialHandles) +} + +func TestProviderToProto_Nil(t *testing.T) { + assert.Nil(t, ProviderToProto(nil)) +} + +func TestProviderToProto_Full(t *testing.T) { + expires := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) + provider := &types.Provider{ + ID: "prov-1", + Name: "test-provider", + Type: "claude", + Labels: map[string]string{"env": "dev"}, + Annotations: map[string]string{"note": "x"}, + ResourceVersion: 7, + Workspace: "ws-1", + Spec: types.ProviderSpec{ + Credentials: map[string]string{"token": "abc"}, + Config: map[string]string{"url": "https://example.com"}, + ProfileWorkspace: "global", + CredentialExpiresAt: map[string]time.Time{"token": expires}, + CredentialHandles: map[string]types.CredentialHandle{ + "token": { + Driver: "k8s-secrets", + Handle: "ns/secret-name", + Metadata: map[string]string{"k": "v"}, + }, + }, + }, + } + + result := ProviderToProto(provider) + + require.NotNil(t, result) + assert.Equal(t, "prov-1", result.Metadata.Id) + assert.Equal(t, "test-provider", result.Metadata.Name) + assert.Equal(t, "claude", result.Type) + assert.Equal(t, "global", result.ProfileWorkspace) + assert.Equal(t, map[string]string{"token": "abc"}, result.Credentials) + assert.Equal(t, map[string]string{"url": "https://example.com"}, result.Config) + + require.Len(t, result.CredentialExpiresAtMs, 1) + assert.Greater(t, result.CredentialExpiresAtMs["token"], int64(0)) + + require.Len(t, result.CredentialHandles, 1) + h := result.CredentialHandles["token"] + assert.Equal(t, "k8s-secrets", h.Driver) + assert.Equal(t, "ns/secret-name", h.Handle) + assert.Equal(t, map[string]string{"k": "v"}, h.Metadata) +} + +func TestProviderRoundTrip(t *testing.T) { + original := &types.Provider{ + ID: "rt-1", + Name: "roundtrip", + Type: "gitlab", + ResourceVersion: 3, + Workspace: "default", + Labels: map[string]string{"team": "infra"}, + Spec: types.ProviderSpec{ + Config: map[string]string{"url": "https://gitlab.com"}, + ProfileWorkspace: "shared", + CredentialHandles: map[string]types.CredentialHandle{ + "pat": {Driver: "vault", Handle: "secret/gitlab", Metadata: map[string]string{"ver": "1"}}, + }, + }, + } + + proto := ProviderToProto(original) + back := ProviderFromProto(proto) + + assert.Equal(t, original.ID, back.ID) + assert.Equal(t, original.Name, back.Name) + assert.Equal(t, original.Type, back.Type) + assert.Equal(t, original.Workspace, back.Workspace) + assert.Equal(t, original.Labels, back.Labels) + assert.Equal(t, original.Spec.Config, back.Spec.Config) + assert.Equal(t, original.Spec.ProfileWorkspace, back.Spec.ProfileWorkspace) + assert.Equal(t, original.Spec.CredentialHandles, back.Spec.CredentialHandles) +} diff --git a/sdk/go/openshell/v1/internal/converter/sandbox.go b/sdk/go/openshell/v1/internal/converter/sandbox.go new file mode 100644 index 000000000..b522454b8 --- /dev/null +++ b/sdk/go/openshell/v1/internal/converter/sandbox.go @@ -0,0 +1,206 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package converter + +import ( + "fmt" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" + dm "github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1" + pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1" +) + +// SandboxFromProto converts a proto Sandbox to an SDK Sandbox. +func SandboxFromProto(s *pb.Sandbox) *types.Sandbox { + if s == nil { + return nil + } + + result := &types.Sandbox{} + + if m := s.GetMetadata(); m != nil { + result.ID = m.GetId() + result.Name = m.GetName() + result.CreatedAt = TimeFromMillis(m.GetCreatedAtMs()) + result.Labels = CopyStringMap(m.GetLabels()) + result.Annotations = CopyStringMap(m.GetAnnotations()) + result.ResourceVersion = m.GetResourceVersion() + result.Workspace = m.GetWorkspace() + result.DeletionTimestamp = TimeFromMillisPtr(m.GetDeletionTimestampMs()) + } + + if spec := s.GetSpec(); spec != nil { + result.Spec = sandboxSpecFromProto(spec) + } + + if status := s.GetStatus(); status != nil { + result.Status = sandboxStatusFromProto(status) + } else { + result.Status.Phase = types.SandboxUnknown + } + + return result +} + +func sandboxSpecFromProto(spec *pb.SandboxSpec) types.SandboxSpec { + result := types.SandboxSpec{ + LogLevel: spec.GetLogLevel(), + Environment: CopyStringMap(spec.GetEnvironment()), + Providers: CopyStringSlice(spec.GetProviders()), + Policy: SandboxPolicyFromProto(spec.GetPolicy()), + } + + if tmpl := spec.GetTemplate(); tmpl != nil { + result.Template = &types.SandboxTemplate{ + Image: tmpl.GetImage(), + RuntimeClassName: tmpl.GetRuntimeClassName(), + AgentSocket: tmpl.GetAgentSocket(), + Labels: CopyStringMap(tmpl.GetLabels()), + Annotations: CopyStringMap(tmpl.GetAnnotations()), + Environment: CopyStringMap(tmpl.GetEnvironment()), + Resources: structToMap(tmpl.GetResources()), + UserNamespaces: CopyBoolPtr(tmpl.UserNamespaces), + DriverConfig: structToMap(tmpl.GetDriverConfig()), + } + } + + if rr := spec.GetResourceRequirements(); rr != nil { + if gpu := rr.GetGpu(); gpu != nil && gpu.Count != nil { + result.GPUCount = gpu.Count + } + } + + return result +} + +func sandboxStatusFromProto(status *pb.SandboxStatus) types.SandboxStatus { + result := types.SandboxStatus{ + SandboxName: status.GetSandboxName(), + AgentPod: status.GetAgentPod(), + AgentFd: status.GetAgentFd(), + SandboxFd: status.GetSandboxFd(), + Phase: SandboxPhaseFromProto(status.GetPhase()), + CurrentPolicyVersion: status.GetCurrentPolicyVersion(), + } + + for _, c := range status.GetConditions() { + result.Conditions = append(result.Conditions, types.SandboxCondition{ + Type: c.GetType(), + Status: c.GetStatus(), + Reason: c.GetReason(), + Message: c.GetMessage(), + LastTransitionTime: c.GetLastTransitionTime(), + }) + } + + return result +} + +// SandboxPhaseFromProto converts a proto SandboxPhase to an SDK SandboxPhase. +func SandboxPhaseFromProto(phase pb.SandboxPhase) types.SandboxPhase { + switch phase { + case pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING: + return types.SandboxProvisioning + case pb.SandboxPhase_SANDBOX_PHASE_READY: + return types.SandboxReady + case pb.SandboxPhase_SANDBOX_PHASE_ERROR: + return types.SandboxError + case pb.SandboxPhase_SANDBOX_PHASE_DELETING: + return types.SandboxDeleting + case pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN: + return types.SandboxUnknown + default: + return types.SandboxUnknown + } +} + +// SandboxPhaseToProto converts an SDK SandboxPhase to a proto SandboxPhase. +func SandboxPhaseToProto(phase types.SandboxPhase) pb.SandboxPhase { + switch phase { + case types.SandboxProvisioning: + return pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING + case types.SandboxReady: + return pb.SandboxPhase_SANDBOX_PHASE_READY + case types.SandboxError: + return pb.SandboxPhase_SANDBOX_PHASE_ERROR + case types.SandboxDeleting: + return pb.SandboxPhase_SANDBOX_PHASE_DELETING + case types.SandboxUnknown: + return pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN + default: + return pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN + } +} + +// SandboxToProto converts an SDK Sandbox to a proto Sandbox. +func SandboxToProto(s *types.Sandbox) (*pb.Sandbox, error) { + if s == nil { + return nil, nil + } + + spec, err := SandboxSpecToProto(&s.Spec) + if err != nil { + return nil, fmt.Errorf("convert sandbox spec: %w", err) + } + + return &pb.Sandbox{ + Metadata: &dm.ObjectMeta{ + Id: s.ID, + Name: s.Name, + CreatedAtMs: MillisFromTime(s.CreatedAt), + Labels: CopyStringMap(s.Labels), + Annotations: CopyStringMap(s.Annotations), + ResourceVersion: s.ResourceVersion, + Workspace: s.Workspace, + DeletionTimestampMs: MillisFromTimePtr(s.DeletionTimestamp), + }, + Spec: spec, + }, nil +} + +// SandboxSpecToProto converts an SDK SandboxSpec to a proto SandboxSpec. +func SandboxSpecToProto(spec *types.SandboxSpec) (*pb.SandboxSpec, error) { + if spec == nil { + return nil, nil + } + + result := &pb.SandboxSpec{ + LogLevel: spec.LogLevel, + Environment: CopyStringMap(spec.Environment), + Providers: CopyStringSlice(spec.Providers), + Policy: SandboxPolicyToProto(spec.Policy), + } + + if spec.Template != nil { + resources, err := mapToStruct(spec.Template.Resources) + if err != nil { + return nil, fmt.Errorf("convert template resources: %w", err) + } + driverConfig, err := mapToStruct(spec.Template.DriverConfig) + if err != nil { + return nil, fmt.Errorf("convert template driver config: %w", err) + } + result.Template = &pb.SandboxTemplate{ + Image: spec.Template.Image, + RuntimeClassName: spec.Template.RuntimeClassName, + AgentSocket: spec.Template.AgentSocket, + Labels: CopyStringMap(spec.Template.Labels), + Annotations: CopyStringMap(spec.Template.Annotations), + Environment: CopyStringMap(spec.Template.Environment), + Resources: resources, + UserNamespaces: CopyBoolPtr(spec.Template.UserNamespaces), + DriverConfig: driverConfig, + } + } + + if spec.GPUCount != nil { + result.ResourceRequirements = &pb.ResourceRequirements{ + Gpu: &pb.GpuResourceRequirements{ + Count: spec.GPUCount, + }, + } + } + + return result, nil +} diff --git a/sdk/go/openshell/v1/internal/converter/sandbox_test.go b/sdk/go/openshell/v1/internal/converter/sandbox_test.go new file mode 100644 index 000000000..f3c41650e --- /dev/null +++ b/sdk/go/openshell/v1/internal/converter/sandbox_test.go @@ -0,0 +1,404 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package converter + +import ( + "testing" + "time" + + v1 "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" + dm "github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1" + pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +func TestSandboxFromProto(t *testing.T) { + userNS := true + gpuCount := uint32(2) + proto := &pb.Sandbox{ + Metadata: &dm.ObjectMeta{ + Id: "sb-1", + Name: "my-sandbox", + CreatedAtMs: 1700000000000, + Labels: map[string]string{"env": "dev"}, + Annotations: map[string]string{"owner": "team-a"}, + ResourceVersion: 3, + Workspace: "prod", + DeletionTimestampMs: 1700000060000, + }, + Spec: &pb.SandboxSpec{ + LogLevel: "debug", + Environment: map[string]string{"FOO": "bar"}, + Template: &pb.SandboxTemplate{ + Image: "nvidia/sandbox:latest", + RuntimeClassName: "kata", + AgentSocket: "/var/run/agent.sock", + Labels: map[string]string{"app": "test"}, + Annotations: map[string]string{"note": "hello"}, + Environment: map[string]string{"TMPL_VAR": "val"}, + UserNamespaces: &userNS, + }, + Providers: []string{"claude", "github"}, + ResourceRequirements: &pb.ResourceRequirements{ + Gpu: &pb.GpuResourceRequirements{ + Count: &gpuCount, + }, + }, + }, + Status: &pb.SandboxStatus{ + SandboxName: "sb-compute-1", + AgentPod: "agent-pod-xyz", + AgentFd: "fd-agent", + SandboxFd: "fd-sandbox", + Phase: pb.SandboxPhase_SANDBOX_PHASE_READY, + CurrentPolicyVersion: 7, + Conditions: []*pb.SandboxCondition{ + { + Type: "Ready", + Status: "True", + Reason: "AllGood", + Message: "Sandbox is ready", + LastTransitionTime: "2024-01-01T00:00:00Z", + }, + }, + }, + } + + s := SandboxFromProto(proto) + + require.NotNil(t, s) + assert.Equal(t, "sb-1", s.ID) + assert.Equal(t, "my-sandbox", s.Name) + assert.Equal(t, time.UnixMilli(1700000000000).UTC(), s.CreatedAt) + assert.Equal(t, map[string]string{"env": "dev"}, s.Labels) + assert.Equal(t, map[string]string{"owner": "team-a"}, s.Annotations) + assert.Equal(t, uint64(3), s.ResourceVersion) + assert.Equal(t, "prod", s.Workspace) + require.NotNil(t, s.DeletionTimestamp) + assert.Equal(t, time.UnixMilli(1700000060000).UTC(), *s.DeletionTimestamp) + + // Spec + assert.Equal(t, "debug", s.Spec.LogLevel) + assert.Equal(t, map[string]string{"FOO": "bar"}, s.Spec.Environment) + assert.Equal(t, []string{"claude", "github"}, s.Spec.Providers) + require.NotNil(t, s.Spec.GPUCount) + assert.Equal(t, uint32(2), *s.Spec.GPUCount) + + // Template + require.NotNil(t, s.Spec.Template) + assert.Equal(t, "nvidia/sandbox:latest", s.Spec.Template.Image) + assert.Equal(t, "kata", s.Spec.Template.RuntimeClassName) + assert.Equal(t, "/var/run/agent.sock", s.Spec.Template.AgentSocket) + assert.Equal(t, map[string]string{"app": "test"}, s.Spec.Template.Labels) + assert.Equal(t, map[string]string{"note": "hello"}, s.Spec.Template.Annotations) + assert.Equal(t, map[string]string{"TMPL_VAR": "val"}, s.Spec.Template.Environment) + require.NotNil(t, s.Spec.Template.UserNamespaces) + assert.True(t, *s.Spec.Template.UserNamespaces) + + // Status + assert.Equal(t, "sb-compute-1", s.Status.SandboxName) + assert.Equal(t, "agent-pod-xyz", s.Status.AgentPod) + assert.Equal(t, "fd-agent", s.Status.AgentFd) + assert.Equal(t, "fd-sandbox", s.Status.SandboxFd) + assert.Equal(t, v1.SandboxReady, s.Status.Phase) + assert.Equal(t, uint32(7), s.Status.CurrentPolicyVersion) + require.Len(t, s.Status.Conditions, 1) + assert.Equal(t, "Ready", s.Status.Conditions[0].Type) + assert.Equal(t, "True", s.Status.Conditions[0].Status) + assert.Equal(t, "AllGood", s.Status.Conditions[0].Reason) + assert.Equal(t, "Sandbox is ready", s.Status.Conditions[0].Message) + assert.Equal(t, "2024-01-01T00:00:00Z", s.Status.Conditions[0].LastTransitionTime) +} + +func TestSandboxFromProto_NilFields(t *testing.T) { + proto := &pb.Sandbox{} + + s := SandboxFromProto(proto) + + require.NotNil(t, s) + assert.Empty(t, s.ID) + assert.Empty(t, s.Name) + assert.True(t, s.CreatedAt.IsZero()) + assert.Nil(t, s.Spec.Template) + assert.Nil(t, s.Spec.GPUCount) + assert.Equal(t, v1.SandboxUnknown, s.Status.Phase) +} + +func TestSandboxFromProto_Nil(t *testing.T) { + s := SandboxFromProto(nil) + assert.Nil(t, s) +} + +func TestSandboxPhaseFromProto(t *testing.T) { + tests := []struct { + proto pb.SandboxPhase + expected v1.SandboxPhase + }{ + {pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING, v1.SandboxProvisioning}, + {pb.SandboxPhase_SANDBOX_PHASE_READY, v1.SandboxReady}, + {pb.SandboxPhase_SANDBOX_PHASE_ERROR, v1.SandboxError}, + {pb.SandboxPhase_SANDBOX_PHASE_DELETING, v1.SandboxDeleting}, + {pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN, v1.SandboxUnknown}, + {pb.SandboxPhase_SANDBOX_PHASE_UNSPECIFIED, v1.SandboxUnknown}, + {pb.SandboxPhase(999), v1.SandboxUnknown}, + } + + for _, tt := range tests { + assert.Equal(t, tt.expected, SandboxPhaseFromProto(tt.proto), "phase %v", tt.proto) + } +} + +func TestSandboxPhaseToProto(t *testing.T) { + tests := []struct { + sdk v1.SandboxPhase + expected pb.SandboxPhase + }{ + {v1.SandboxProvisioning, pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + {v1.SandboxReady, pb.SandboxPhase_SANDBOX_PHASE_READY}, + {v1.SandboxError, pb.SandboxPhase_SANDBOX_PHASE_ERROR}, + {v1.SandboxDeleting, pb.SandboxPhase_SANDBOX_PHASE_DELETING}, + {v1.SandboxUnknown, pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN}, + {v1.SandboxPhase("bogus"), pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN}, + } + + for _, tt := range tests { + assert.Equal(t, tt.expected, SandboxPhaseToProto(tt.sdk), "phase %v", tt.sdk) + } +} + +func TestSandboxToProto(t *testing.T) { + userNS := true + gpuCount := uint32(4) + delTime := time.UnixMilli(1700000060000).UTC() + s := &v1.Sandbox{ + ID: "sb-1", + Name: "my-sandbox", + CreatedAt: time.UnixMilli(1700000000000).UTC(), + Labels: map[string]string{"env": "dev"}, + Annotations: map[string]string{"owner": "team-a"}, + ResourceVersion: 3, + Workspace: "prod", + DeletionTimestamp: &delTime, + Spec: v1.SandboxSpec{ + LogLevel: "info", + Environment: map[string]string{"KEY": "val"}, + Template: &v1.SandboxTemplate{ + Image: "img:v1", + RuntimeClassName: "runc", + AgentSocket: "/sock", + Labels: map[string]string{"l": "v"}, + Annotations: map[string]string{"a": "v"}, + Environment: map[string]string{"E": "V"}, + UserNamespaces: &userNS, + }, + Providers: []string{"prov-a"}, + GPUCount: &gpuCount, + }, + } + + p, err := SandboxToProto(s) + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.Metadata) + assert.Equal(t, "sb-1", p.Metadata.Id) + assert.Equal(t, "my-sandbox", p.Metadata.Name) + assert.Equal(t, int64(1700000000000), p.Metadata.CreatedAtMs) + assert.Equal(t, map[string]string{"env": "dev"}, p.Metadata.Labels) + assert.Equal(t, map[string]string{"owner": "team-a"}, p.Metadata.Annotations) + assert.Equal(t, uint64(3), p.Metadata.ResourceVersion) + assert.Equal(t, "prod", p.Metadata.Workspace) + assert.Equal(t, int64(1700000060000), p.Metadata.DeletionTimestampMs) + + require.NotNil(t, p.Spec) + assert.Equal(t, "info", p.Spec.LogLevel) + assert.Equal(t, map[string]string{"KEY": "val"}, p.Spec.Environment) + assert.Equal(t, []string{"prov-a"}, p.Spec.Providers) + + require.NotNil(t, p.Spec.ResourceRequirements) + require.NotNil(t, p.Spec.ResourceRequirements.Gpu) + assert.Equal(t, uint32(4), p.Spec.ResourceRequirements.Gpu.GetCount()) + + require.NotNil(t, p.Spec.Template) + assert.Equal(t, "img:v1", p.Spec.Template.Image) + assert.Equal(t, "runc", p.Spec.Template.RuntimeClassName) + assert.Equal(t, "/sock", p.Spec.Template.AgentSocket) + assert.Equal(t, map[string]string{"l": "v"}, p.Spec.Template.Labels) + assert.Equal(t, map[string]string{"a": "v"}, p.Spec.Template.Annotations) + assert.Equal(t, map[string]string{"E": "V"}, p.Spec.Template.Environment) + require.NotNil(t, p.Spec.Template.UserNamespaces) + assert.True(t, *p.Spec.Template.UserNamespaces) +} + +func TestSandboxToProto_Nil(t *testing.T) { + p, err := SandboxToProto(nil) + require.NoError(t, err) + assert.Nil(t, p) +} + +func TestSandboxToProto_NilTemplate(t *testing.T) { + s := &v1.Sandbox{ + Spec: v1.SandboxSpec{ + LogLevel: "warn", + }, + } + + p, err := SandboxToProto(s) + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.Spec) + assert.Nil(t, p.Spec.Template) + assert.Nil(t, p.Spec.ResourceRequirements) +} + +func TestSandboxRoundTrip(t *testing.T) { + userNS := false + gpuCount := uint32(1) + rtDelTime := time.UnixMilli(1700000090000).UTC() + original := &v1.Sandbox{ + ID: "sb-rt", + Name: "round-trip", + CreatedAt: time.UnixMilli(1700000000000).UTC(), + Labels: map[string]string{"team": "platform"}, + Annotations: map[string]string{"note": "rt-test"}, + ResourceVersion: 10, + Workspace: "staging", + DeletionTimestamp: &rtDelTime, + Spec: v1.SandboxSpec{ + LogLevel: "trace", + Environment: map[string]string{"A": "B"}, + Template: &v1.SandboxTemplate{ + Image: "img:rt", + UserNamespaces: &userNS, + }, + Providers: []string{"p1", "p2"}, + GPUCount: &gpuCount, + Policy: &v1.SandboxPolicy{ + Version: 3, + Filesystem: &v1.FilesystemPolicy{ + IncludeWorkdir: true, + ReadOnly: []string{"/etc", "/usr/share"}, + ReadWrite: []string{"/tmp"}, + }, + Landlock: &v1.LandlockPolicy{ + Compatibility: "best_effort", + }, + Process: &v1.ProcessPolicy{ + RunAsUser: "sandbox", + RunAsGroup: "sandbox-group", + }, + NetworkPolicies: map[string]v1.NetworkPolicyRule{ + "web": { + Name: "web", + Endpoints: []v1.PolicyNetworkEndpoint{ + {Host: "api.example.com", Port: 443, Protocol: "rest"}, + }, + }, + }, + }, + }, + } + + p, err := SandboxToProto(original) + require.NoError(t, err) + back := SandboxFromProto(p) + + assert.Equal(t, original.ID, back.ID) + assert.Equal(t, original.Name, back.Name) + assert.Equal(t, original.CreatedAt, back.CreatedAt) + assert.Equal(t, original.Labels, back.Labels) + assert.Equal(t, original.Annotations, back.Annotations) + assert.Equal(t, original.ResourceVersion, back.ResourceVersion) + assert.Equal(t, original.Workspace, back.Workspace) + require.NotNil(t, back.DeletionTimestamp) + assert.Equal(t, *original.DeletionTimestamp, *back.DeletionTimestamp) + assert.Equal(t, original.Spec.LogLevel, back.Spec.LogLevel) + assert.Equal(t, original.Spec.Environment, back.Spec.Environment) + assert.Equal(t, original.Spec.Providers, back.Spec.Providers) + require.NotNil(t, back.Spec.GPUCount) + assert.Equal(t, *original.Spec.GPUCount, *back.Spec.GPUCount) + require.NotNil(t, back.Spec.Template) + assert.Equal(t, original.Spec.Template.Image, back.Spec.Template.Image) + require.NotNil(t, back.Spec.Template.UserNamespaces) + assert.Equal(t, *original.Spec.Template.UserNamespaces, *back.Spec.Template.UserNamespaces) + + // Policy round-trip + require.NotNil(t, back.Spec.Policy) + assert.Equal(t, uint32(3), back.Spec.Policy.Version) + require.NotNil(t, back.Spec.Policy.Filesystem) + assert.True(t, back.Spec.Policy.Filesystem.IncludeWorkdir) + assert.Equal(t, []string{"/etc", "/usr/share"}, back.Spec.Policy.Filesystem.ReadOnly) + assert.Equal(t, []string{"/tmp"}, back.Spec.Policy.Filesystem.ReadWrite) + require.NotNil(t, back.Spec.Policy.Landlock) + assert.Equal(t, "best_effort", back.Spec.Policy.Landlock.Compatibility) + require.NotNil(t, back.Spec.Policy.Process) + assert.Equal(t, "sandbox", back.Spec.Policy.Process.RunAsUser) + assert.Equal(t, "sandbox-group", back.Spec.Policy.Process.RunAsGroup) + require.Len(t, back.Spec.Policy.NetworkPolicies, 1) + webRule, ok := back.Spec.Policy.NetworkPolicies["web"] + require.True(t, ok) + assert.Equal(t, "web", webRule.Name) + require.Len(t, webRule.Endpoints, 1) + assert.Equal(t, "api.example.com", webRule.Endpoints[0].Host) +} + +func TestSandboxSpecToProto(t *testing.T) { + gpuCount := uint32(3) + spec := &v1.SandboxSpec{ + LogLevel: "debug", + Environment: map[string]string{"X": "Y"}, + Template: &v1.SandboxTemplate{ + Image: "img:spec", + }, + Providers: []string{"prov"}, + GPUCount: &gpuCount, + Policy: &v1.SandboxPolicy{ + Version: 2, + Filesystem: &v1.FilesystemPolicy{ + ReadOnly: []string{"/etc"}, + }, + }, + } + + p, err := SandboxSpecToProto(spec) + require.NoError(t, err) + require.NotNil(t, p) + assert.Equal(t, "debug", p.LogLevel) + assert.Equal(t, map[string]string{"X": "Y"}, p.Environment) + assert.Equal(t, []string{"prov"}, p.Providers) + require.NotNil(t, p.ResourceRequirements) + assert.Equal(t, uint32(3), p.ResourceRequirements.Gpu.GetCount()) + require.NotNil(t, p.Template) + assert.Equal(t, "img:spec", p.Template.Image) + + // Policy conversion + require.NotNil(t, p.Policy) + assert.Equal(t, uint32(2), p.Policy.Version) + require.NotNil(t, p.Policy.Filesystem) + assert.Equal(t, []string{"/etc"}, p.Policy.Filesystem.ReadOnly) +} + +func TestSandboxSpecToProto_Nil(t *testing.T) { + p, err := SandboxSpecToProto(nil) + require.NoError(t, err) + assert.Nil(t, p) +} + +func TestSandboxSpecToProto_InvalidMapReturnsError(t *testing.T) { + spec := &v1.SandboxSpec{ + Template: &v1.SandboxTemplate{ + Image: "img:v1", + Resources: map[string]any{"bad": make(chan int)}, + }, + } + + p, err := SandboxSpecToProto(spec) + require.Error(t, err, "SandboxSpecToProto must return an error for unconvertible map values") + assert.Nil(t, p) + assert.Contains(t, err.Error(), "convert template resources") +} + +// Verify proto import is used (suppress unused import warning). +var _ = proto.Marshal diff --git a/sdk/go/openshell/v1/internal/converter/time.go b/sdk/go/openshell/v1/internal/converter/time.go new file mode 100644 index 000000000..28a633cdf --- /dev/null +++ b/sdk/go/openshell/v1/internal/converter/time.go @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package converter + +import "time" + +// TimeFromMillis converts a millisecond epoch timestamp to time.Time. +// A zero value returns the zero time. +func TimeFromMillis(ms int64) time.Time { + if ms == 0 { + return time.Time{} + } + return time.UnixMilli(ms).UTC() +} + +// MillisFromTime converts a time.Time to a millisecond epoch timestamp. +// A zero time returns 0. +func MillisFromTime(t time.Time) int64 { + if t.IsZero() { + return 0 + } + return t.UnixMilli() +} + +// TimeFromMillisPtr converts a millisecond epoch timestamp to a *time.Time. +// A zero value returns nil (the resource is not being deleted). +func TimeFromMillisPtr(ms int64) *time.Time { + if ms == 0 { + return nil + } + t := time.UnixMilli(ms).UTC() + return &t +} + +// MillisFromTimePtr converts a *time.Time to a millisecond epoch timestamp. +// A nil pointer returns 0. +func MillisFromTimePtr(t *time.Time) int64 { + if t == nil { + return 0 + } + return t.UnixMilli() +} diff --git a/sdk/go/openshell/v1/internal/converter/time_test.go b/sdk/go/openshell/v1/internal/converter/time_test.go new file mode 100644 index 000000000..0b4d44fd2 --- /dev/null +++ b/sdk/go/openshell/v1/internal/converter/time_test.go @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package converter + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestTimeFromMillis(t *testing.T) { + ms := int64(1719475200000) // 2024-06-27T08:00:00Z + tm := TimeFromMillis(ms) + assert.Equal(t, 2024, tm.Year()) + assert.Equal(t, time.June, tm.Month()) + assert.Equal(t, 27, tm.Day()) +} + +func TestTimeFromMillis_Zero(t *testing.T) { + tm := TimeFromMillis(0) + assert.True(t, tm.IsZero()) +} + +func TestMillisFromTime(t *testing.T) { + tm := time.Date(2024, time.June, 27, 12, 0, 0, 0, time.UTC) + ms := MillisFromTime(tm) + assert.Equal(t, int64(1719489600000), ms) +} + +func TestMillisFromTime_Zero(t *testing.T) { + ms := MillisFromTime(time.Time{}) + assert.Equal(t, int64(0), ms) +} + +func TestRoundTrip(t *testing.T) { + original := time.Date(2025, time.March, 15, 10, 30, 0, 0, time.UTC) + ms := MillisFromTime(original) + restored := TimeFromMillis(ms) + assert.Equal(t, original.Unix(), restored.Unix()) +} + +func TestTimeFromMillisPtr_NonZero(t *testing.T) { + ms := int64(1719475200000) + tp := TimeFromMillisPtr(ms) + assert.NotNil(t, tp) + assert.Equal(t, 2024, tp.Year()) +} + +func TestTimeFromMillisPtr_Zero(t *testing.T) { + tp := TimeFromMillisPtr(0) + assert.Nil(t, tp) +} + +func TestMillisFromTimePtr_NonNil(t *testing.T) { + tm := time.Date(2024, time.June, 27, 12, 0, 0, 0, time.UTC) + ms := MillisFromTimePtr(&tm) + assert.Equal(t, int64(1719489600000), ms) +} + +func TestMillisFromTimePtr_Nil(t *testing.T) { + ms := MillisFromTimePtr(nil) + assert.Equal(t, int64(0), ms) +} + +func TestPtrRoundTrip(t *testing.T) { + original := time.Date(2025, time.March, 15, 10, 30, 0, 0, time.UTC) + ms := MillisFromTimePtr(&original) + restored := TimeFromMillisPtr(ms) + assert.NotNil(t, restored) + assert.Equal(t, original.Unix(), restored.Unix()) +} diff --git a/sdk/go/openshell/v1/internal/grpc/conn.go b/sdk/go/openshell/v1/internal/grpc/conn.go new file mode 100644 index 000000000..e2198546a --- /dev/null +++ b/sdk/go/openshell/v1/internal/grpc/conn.go @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Package grpc provides gRPC connection setup utilities. +package grpc + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "os" + "strings" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" +) + +// TLSParams holds TLS settings without importing the v1 package. +type TLSParams struct { + CertFile string + KeyFile string + CAFile string + Insecure bool +} + +// NewConnection creates a gRPC client connection. +// The address may include an http:// or https:// scheme (as written by the +// upstream gateway). The scheme drives transport selection: http:// uses +// plaintext, https:// or no scheme uses TLS. +func NewConnection(address string, tlsCfg *TLSParams, auth credentials.PerRPCCredentials) (*grpc.ClientConn, error) { + usePlaintext := false + if strings.HasPrefix(address, "http://") { + usePlaintext = true + address = strings.TrimPrefix(address, "http://") + } else { + address = strings.TrimPrefix(address, "https://") + } + opts := []grpc.DialOption{} + + if usePlaintext { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + } else if tlsCfg != nil { + creds, err := buildTLSCredentials(tlsCfg) + if err != nil { + return nil, fmt.Errorf("tls config: %w", err) + } + opts = append(opts, grpc.WithTransportCredentials(creds)) + } else { + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS12}))) + } + + if auth != nil { + if usePlaintext && auth.RequireTransportSecurity() { + return nil, fmt.Errorf("grpc connect: auth provider requires transport security but address uses plaintext (http://)") + } + opts = append(opts, grpc.WithPerRPCCredentials(auth)) + } + + conn, err := grpc.NewClient(address, opts...) + if err != nil { + return nil, fmt.Errorf("grpc connect: %w", err) + } + return conn, nil +} + +func buildTLSCredentials(cfg *TLSParams) (credentials.TransportCredentials, error) { + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + InsecureSkipVerify: cfg.Insecure, //nolint:gosec // user-requested skip for dev gateways + } + + if cfg.CAFile != "" { + caCert, err := os.ReadFile(cfg.CAFile) + if err != nil { + return nil, fmt.Errorf("read CA file: %w", err) + } + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM(caCert) { + return nil, fmt.Errorf("invalid CA certificate") + } + tlsConfig.RootCAs = pool + } + + if cfg.CertFile != "" && cfg.KeyFile != "" { + cert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile) + if err != nil { + return nil, fmt.Errorf("load client cert: %w", err) + } + tlsConfig.Certificates = []tls.Certificate{cert} + } else if cfg.CertFile != "" || cfg.KeyFile != "" { + return nil, fmt.Errorf("both CertFile and KeyFile must be provided for client certificate authentication") + } + + return credentials.NewTLS(tlsConfig), nil +} diff --git a/sdk/go/openshell/v1/internal/grpc/conn_test.go b/sdk/go/openshell/v1/internal/grpc/conn_test.go new file mode 100644 index 000000000..a1da2883e --- /dev/null +++ b/sdk/go/openshell/v1/internal/grpc/conn_test.go @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package grpc + +import ( + "context" + "net" + "testing" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +func TestNewConnectionHTTPSchemeUsesPlaintext(t *testing.T) { + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen: %v", err) + } + defer func() { _ = lis.Close() }() + + srv := grpc.NewServer() + go func() { _ = srv.Serve(lis) }() + defer srv.Stop() + + conn, err := NewConnection("http://"+lis.Addr().String(), nil, nil) + if err != nil { + t.Fatalf("NewConnection with http:// scheme failed: %v", err) + } + defer func() { _ = conn.Close() }() +} + +func TestNewConnectionHTTPSSchemeUsesTLS(t *testing.T) { + // https:// with nil TLS config should default to system TLS. + // We cannot dial a real TLS server here, but we can verify the + // connection is created (it will fail on handshake, not on dial). + conn, err := NewConnection("https://127.0.0.1:1", nil, nil) + if err != nil { + t.Fatalf("NewConnection with https:// scheme should not fail on create: %v", err) + } + defer func() { _ = conn.Close() }() +} + +func TestNewConnectionNoSchemeUsesTLS(t *testing.T) { + conn, err := NewConnection("127.0.0.1:1", nil, nil) + if err != nil { + t.Fatalf("NewConnection without scheme should not fail on create: %v", err) + } + defer func() { _ = conn.Close() }() +} + +func TestNewConnectionInsecureTLSConfig(t *testing.T) { + // Insecure: true means TLS with InsecureSkipVerify, not plaintext. + // We can verify the connection is created (handshake will fail since + // the server is not TLS, but NewClient itself should succeed). + conn, err := NewConnection("127.0.0.1:1", &TLSParams{Insecure: true}, nil) + if err != nil { + t.Fatalf("NewConnection with Insecure TLS config failed: %v", err) + } + defer func() { _ = conn.Close() }() +} + +func TestNewConnectionHTTPWithSecureAuthRejects(t *testing.T) { + auth := &testTokenAuth{token: "dev-token", requireSecurity: true} + _, err := NewConnection("http://127.0.0.1:1", nil, auth) + if err == nil { + t.Fatal("expected error when using http:// with auth that requires transport security") + } +} + +func TestNewConnectionHTTPWithInsecureAuth(t *testing.T) { + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen: %v", err) + } + defer func() { _ = lis.Close() }() + + srv := grpc.NewServer(grpc.Creds(insecure.NewCredentials())) + go func() { _ = srv.Serve(lis) }() + defer srv.Stop() + + auth := &testTokenAuth{token: "dev-token", requireSecurity: false} + conn, err := NewConnection("http://"+lis.Addr().String(), nil, auth) + if err != nil { + t.Fatalf("NewConnection with http:// + insecure auth failed: %v", err) + } + defer func() { _ = conn.Close() }() +} + +type testTokenAuth struct { + token string + requireSecurity bool +} + +func (a *testTokenAuth) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { + return map[string]string{"authorization": "Bearer " + a.token}, nil +} + +func (a *testTokenAuth) RequireTransportSecurity() bool { + return a.requireSecurity +} diff --git a/sdk/go/openshell/v1/logger.go b/sdk/go/openshell/v1/logger.go new file mode 100644 index 000000000..e8274012a --- /dev/null +++ b/sdk/go/openshell/v1/logger.go @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// Logger defines structured logging for the SDK. Compatible with logr.Logger +// and slog.Logger adapters. +type Logger = types.Logger diff --git a/sdk/go/openshell/v1/options.go b/sdk/go/openshell/v1/options.go new file mode 100644 index 000000000..cb165b23a --- /dev/null +++ b/sdk/go/openshell/v1/options.go @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// CreateOptions configures resource creation. +type CreateOptions = types.CreateOptions + +// GetOptions configures resource retrieval. +type GetOptions = types.GetOptions + +// ListOptions configures resource listing with pagination and filtering. +type ListOptions = types.ListOptions + +// DeleteOptions configures resource deletion. +type DeleteOptions = types.DeleteOptions + +// UpdateOptions configures resource updates. +type UpdateOptions = types.UpdateOptions + +// WatchOptions configures watch behavior. +type WatchOptions = types.WatchOptions + +// WaitOptions configures wait behavior. Use context for timeout control. +type WaitOptions = types.WaitOptions + +// ExecOptions configures command execution. +type ExecOptions = types.ExecOptions diff --git a/sdk/go/openshell/v1/policy.go b/sdk/go/openshell/v1/policy.go new file mode 100644 index 000000000..b6e5070d9 --- /dev/null +++ b/sdk/go/openshell/v1/policy.go @@ -0,0 +1,173 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// SandboxPolicy is the top-level security policy configuration for a sandbox. +type SandboxPolicy = types.SandboxPolicy + +// FilesystemPolicy controls which directories the sandbox can access. +type FilesystemPolicy = types.FilesystemPolicy + +// LandlockPolicy configures the Linux Landlock LSM. +type LandlockPolicy = types.LandlockPolicy + +// ProcessPolicy controls the user and group identity for sandboxed processes. +type ProcessPolicy = types.ProcessPolicy + +// PolicyChunk represents a single proposed policy change in the draft inbox. +type PolicyChunk = types.PolicyChunk + +// DraftPolicy contains the full draft policy state returned by GetDraft. +type DraftPolicy = types.DraftPolicy + +// PolicyStatusResult contains the status of a sandbox's policy. +type PolicyStatusResult = types.PolicyStatusResult + +// SandboxPolicyRevision represents a versioned policy revision for a sandbox. +type SandboxPolicyRevision = types.SandboxPolicyRevision + +// PolicyLoadStatus represents the load state of a policy revision. +type PolicyLoadStatus = types.PolicyLoadStatus + +// PolicyLoadStatus constants re-exported from types package. +const ( + PolicyLoadStatusUnspecified = types.PolicyLoadStatusUnspecified + PolicyLoadStatusPending = types.PolicyLoadStatusPending + PolicyLoadStatusLoaded = types.PolicyLoadStatusLoaded + PolicyLoadStatusFailed = types.PolicyLoadStatusFailed + PolicyLoadStatusSuperseded = types.PolicyLoadStatusSuperseded +) + +// ApproveResult contains the result of approving a single draft chunk. +type ApproveResult = types.ApproveResult + +// ApproveAllResult contains the result of approving all draft chunks. +type ApproveAllResult = types.ApproveAllResult + +// UndoResult contains the result of undoing a draft chunk approval. +type UndoResult = types.UndoResult + +// ClearResult contains the result of clearing all draft chunks. +type ClearResult = types.ClearResult + +// DraftHistoryEntry represents a single event in the draft policy history. +type DraftHistoryEntry = types.DraftHistoryEntry + +// GetDraftOption configures a GetDraft call. +type GetDraftOption = types.GetDraftOption + +// WithStatusFilter filters draft chunks by approval status. +var WithStatusFilter = types.WithStatusFilter + +// ApproveAllOption configures an ApproveAllDraftChunks call. +type ApproveAllOption = types.ApproveAllOption + +// WithIncludeSecurityFlagged includes security-flagged chunks in bulk approval. +var WithIncludeSecurityFlagged = types.WithIncludeSecurityFlagged + +// GetStatusOption configures a GetStatus call. +type GetStatusOption = types.GetStatusOption + +// WithVersion queries a specific policy version instead of the latest. +var WithVersion = types.WithVersion + +// ListPolicyOption configures a List call. +type ListPolicyOption = types.ListPolicyOption + +// WithLimit sets the maximum number of revisions to return. +var WithLimit = types.WithLimit + +// WithOffset sets the pagination offset. +var WithOffset = types.WithOffset + +// PolicyInterface defines operations for managing sandbox policy drafts, +// approvals, and revision history. +type PolicyInterface interface { + // GetDraft retrieves the current draft policy for a sandbox, including + // all pending, approved, and rejected chunks. Use WithStatusFilter to + // return only chunks matching a specific status. + // + // Errors: NotFound if the sandbox does not exist; InvalidArgument if the + // sandbox name is empty; Unimplemented by the fake client. + GetDraft(ctx context.Context, workspace, sandboxName string, opts ...GetDraftOption) (*DraftPolicy, error) + + // ApproveDraftChunk approves a single pending draft chunk, merging + // its proposed rule into the active policy. + // + // Errors: NotFound if the sandbox or chunk does not exist; + // InvalidArgument if the sandbox name or chunk ID is empty; + // Conflict if the chunk has already been approved or rejected; + // Unimplemented by the fake client. + ApproveDraftChunk(ctx context.Context, workspace, sandboxName, chunkID string) (*ApproveResult, error) + + // RejectDraftChunk rejects a single pending draft chunk with an + // optional reason that is fed to future LLM analysis context. + // + // Errors: NotFound if the sandbox or chunk does not exist; + // InvalidArgument if the sandbox name or chunk ID is empty; + // Conflict if the chunk has already been approved or rejected; + // Unimplemented by the fake client. + RejectDraftChunk(ctx context.Context, workspace, sandboxName, chunkID, reason string) error + + // ApproveAllDraftChunks approves all pending draft chunks at once. + // By default, security-flagged chunks are skipped. Use + // WithIncludeSecurityFlagged to include them. + // + // Errors: NotFound if the sandbox does not exist; InvalidArgument if + // the sandbox name is empty; Unimplemented by the fake client. + ApproveAllDraftChunks(ctx context.Context, workspace, sandboxName string, opts ...ApproveAllOption) (*ApproveAllResult, error) + + // ClearDraftChunks removes all pending draft chunks for a sandbox. + // + // Errors: NotFound if the sandbox does not exist; InvalidArgument if + // the sandbox name is empty; Unimplemented by the fake client. + ClearDraftChunks(ctx context.Context, workspace, sandboxName string) (*ClearResult, error) + + // GetDraftHistory returns the chronological decision history for a + // sandbox's draft policy (approvals, rejections, edits, undos, clears). + // + // Errors: NotFound if the sandbox does not exist; InvalidArgument if + // the sandbox name is empty; Unimplemented by the fake client. + GetDraftHistory(ctx context.Context, workspace, sandboxName string) ([]DraftHistoryEntry, error) + + // GetStatus retrieves the policy status for a sandbox, including the + // queried revision and the active version. Use WithVersion to query a + // specific version instead of the latest. + // + // Errors: NotFound if the sandbox or requested version does not exist; + // InvalidArgument if the sandbox name is empty; + // Unimplemented by the fake client. + GetStatus(ctx context.Context, workspace, sandboxName string, opts ...GetStatusOption) (*PolicyStatusResult, error) + + // List returns policy revisions for a sandbox, ordered by version. + // Use WithLimit and WithOffset for pagination. + // + // Errors: NotFound if the sandbox does not exist; InvalidArgument if + // the sandbox name is empty; Unimplemented by the fake client. + List(ctx context.Context, workspace string, opts ...ListPolicyOption) ([]SandboxPolicyRevision, error) + + // EditDraftChunk replaces the proposed rule of a pending draft chunk + // with the given network policy rule. + // + // Errors: NotFound if the sandbox or chunk does not exist; + // InvalidArgument if the sandbox name, chunk ID, or proposed rule is + // empty/nil; Conflict if the chunk is not in a pending state; + // Unimplemented by the fake client. + EditDraftChunk(ctx context.Context, workspace, sandboxName, chunkID string, proposedRule *NetworkPolicyRule) error + + // UndoDraftChunk reverses a previously approved chunk, removing its + // merged rule from the active policy. + // + // Errors: NotFound if the sandbox or chunk does not exist; + // InvalidArgument if the sandbox name or chunk ID is empty; + // Conflict if the chunk has not been approved; + // Unimplemented by the fake client. + UndoDraftChunk(ctx context.Context, workspace, sandboxName, chunkID string) (*UndoResult, error) +} diff --git a/sdk/go/openshell/v1/profile.go b/sdk/go/openshell/v1/profile.go new file mode 100644 index 000000000..7a91632d5 --- /dev/null +++ b/sdk/go/openshell/v1/profile.go @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// ProviderProfile represents a provider type template. +type ProviderProfile = types.ProviderProfile + +// ProfileCredential defines a single credential required by a provider profile. +type ProfileCredential = types.ProfileCredential + +// ProfileCategory classifies a provider profile. +type ProfileCategory = types.ProfileCategory + +// NetworkEndpoint describes a network endpoint provided by a profile. +type NetworkEndpoint = types.NetworkEndpoint + +// NetworkBinary describes a binary artifact provided by a profile. +type NetworkBinary = types.NetworkBinary + +// ProfileDiscovery holds local discovery configuration for a profile. +type ProfileDiscovery = types.ProfileDiscovery + +// ProfileImportItem is an item submitted for profile import or lint validation. +type ProfileImportItem = types.ProfileImportItem + +// ProfileDiagnostic is a validation finding from Import, Update, or Lint. +type ProfileDiagnostic = types.ProfileDiagnostic + +// ImportResult holds the result of a profile import operation. +type ImportResult = types.ImportResult + +// UpdateResult holds the result of a profile update operation. +type UpdateResult = types.UpdateResult + +// LintResult holds the result of a profile lint operation. +type LintResult = types.LintResult + +// ProfileCategory values. +const ( + ProfileCategoryOther = types.ProfileCategoryOther + ProfileCategoryInference = types.ProfileCategoryInference + ProfileCategoryAgent = types.ProfileCategoryAgent + ProfileCategorySourceControl = types.ProfileCategorySourceControl + ProfileCategoryMessaging = types.ProfileCategoryMessaging + ProfileCategoryData = types.ProfileCategoryData + ProfileCategoryKnowledge = types.ProfileCategoryKnowledge +) + +// ProfileInterface defines operations for managing provider profiles. +type ProfileInterface interface { + // List returns all provider profiles. + List(ctx context.Context, workspace string, opts ...ListOptions) ([]*ProviderProfile, error) + // Get retrieves a provider profile by ID. + Get(ctx context.Context, workspace, id string) (*ProviderProfile, error) + // Import submits profiles for import and returns the result with diagnostics. + Import(ctx context.Context, workspace string, items []ProfileImportItem) (*ImportResult, error) + // Update replaces an existing profile identified by ID and expected resource version. + Update(ctx context.Context, workspace, id string, expectedResourceVersion uint64, item ProfileImportItem) (*UpdateResult, error) + // Lint validates profiles without persisting them and returns diagnostics. + Lint(ctx context.Context, workspace string, items []ProfileImportItem) (*LintResult, error) + // Delete removes a provider profile by ID. Returns true if deleted. + Delete(ctx context.Context, workspace, id string) (bool, error) +} diff --git a/sdk/go/openshell/v1/provider.go b/sdk/go/openshell/v1/provider.go new file mode 100644 index 000000000..f1ab67c48 --- /dev/null +++ b/sdk/go/openshell/v1/provider.go @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// Provider represents an AI provider registration. +type Provider = types.Provider + +// ProviderSpec holds provider-specific configuration and credentials. +type ProviderSpec = types.ProviderSpec + +// ProviderInterface defines CRUD and Ensure operations on providers, +// plus sub-client accessors for profiles and credential refresh. +type ProviderInterface interface { + Create(ctx context.Context, workspace string, provider *Provider) (*Provider, error) + Get(ctx context.Context, workspace, name string) (*Provider, error) + List(ctx context.Context, workspace string, opts ...ListOptions) ([]*Provider, error) + Update(ctx context.Context, workspace string, provider *Provider) (*Provider, error) + Delete(ctx context.Context, workspace, name string) error + Ensure(ctx context.Context, workspace string, provider *Provider) (*Provider, error) + Profiles() ProfileInterface + Refresh() RefreshInterface +} diff --git a/sdk/go/openshell/v1/refresh.go b/sdk/go/openshell/v1/refresh.go new file mode 100644 index 000000000..6aec9bbc5 --- /dev/null +++ b/sdk/go/openshell/v1/refresh.go @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// RefreshStrategy describes how credentials are refreshed. +type RefreshStrategy = types.RefreshStrategy + +// RefreshStatus reports the current state of credential refresh for a provider credential. +type RefreshStatus = types.RefreshStatus + +// RefreshConfig holds configuration parameters for credential refresh. +type RefreshConfig = types.RefreshConfig + +// RefreshStrategy values. +const ( + RefreshStrategyStatic = types.RefreshStrategyStatic + RefreshStrategyExternal = types.RefreshStrategyExternal + RefreshStrategyOAuth2RefreshToken = types.RefreshStrategyOAuth2RefreshToken + RefreshStrategyOAuth2ClientCredentials = types.RefreshStrategyOAuth2ClientCredentials + RefreshStrategyGoogleServiceAccountJWT = types.RefreshStrategyGoogleServiceAccountJWT + RefreshStrategyAWSStsAssumeRole = types.RefreshStrategyAWSStsAssumeRole +) + +// RefreshInterface defines operations for managing provider credential refresh. +type RefreshInterface interface { + // GetStatus returns the refresh status for a provider's credential. + // If credentialKey is empty, statuses for all credentials are returned. + GetStatus(ctx context.Context, workspace, provider, credentialKey string) ([]*RefreshStatus, error) + // Configure sets up credential refresh for a provider credential. + Configure(ctx context.Context, workspace string, config *RefreshConfig) (*RefreshStatus, error) + // Rotate triggers an immediate credential rotation. + Rotate(ctx context.Context, workspace, provider, credentialKey string) (*RefreshStatus, error) + // Delete removes credential refresh configuration. Returns true if deleted. + Delete(ctx context.Context, workspace, provider, credentialKey string) (bool, error) +} diff --git a/sdk/go/openshell/v1/sandbox.go b/sdk/go/openshell/v1/sandbox.go new file mode 100644 index 000000000..2dfc6ba8a --- /dev/null +++ b/sdk/go/openshell/v1/sandbox.go @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// Sandbox represents a sandbox instance. +type Sandbox = types.Sandbox + +// SandboxSpec holds the desired state of a sandbox. +type SandboxSpec = types.SandboxSpec + +// SandboxTemplate defines the container template for a sandbox. +type SandboxTemplate = types.SandboxTemplate + +// SandboxStatus holds the observed state of a sandbox. +type SandboxStatus = types.SandboxStatus + +// SandboxCondition describes an observed condition of a sandbox. +type SandboxCondition = types.SandboxCondition + +// AttachProviderResult holds the result of attaching a provider to a sandbox. +type AttachProviderResult = types.AttachProviderResult + +// DetachProviderResult holds the result of detaching a provider from a sandbox. +type DetachProviderResult = types.DetachProviderResult + +// LogLine represents a single log entry from a sandbox. +type LogLine = types.LogLine + +// LogResult contains the result of a GetLogs call. +type LogResult = types.LogResult + +// LogOption configures a GetLogs call. +type LogOption = types.LogOption + +// WithLogLines sets the maximum number of log lines to return. +var WithLogLines = types.WithLogLines + +// WithLogSince filters logs to entries at or after the given time. +var WithLogSince = types.WithLogSince + +// WithLogSources filters logs by source (e.g., "gateway", "sandbox"). +var WithLogSources = types.WithLogSources + +// WithLogMinLevel sets the minimum log level to include. +var WithLogMinLevel = types.WithLogMinLevel + +// SandboxInterface defines lifecycle operations on sandboxes. +type SandboxInterface interface { + Create(ctx context.Context, workspace, name string, spec *SandboxSpec, labels map[string]string) (*Sandbox, error) + Get(ctx context.Context, workspace, name string) (*Sandbox, error) + List(ctx context.Context, workspace string, opts ...ListOptions) ([]*Sandbox, error) + Delete(ctx context.Context, workspace, name string) error + AttachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*AttachProviderResult, error) + DetachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*DetachProviderResult, error) + ListProviders(ctx context.Context, workspace, sandboxName string) ([]*Provider, error) + WaitReady(ctx context.Context, workspace, name string, opts ...WaitOptions) (*Sandbox, error) + Watch(ctx context.Context, workspace, name string, opts ...WatchOptions) (WatchInterface[*Sandbox], error) + // GetLogs retrieves log entries for a sandbox. The sandbox is resolved + // by name (an internal Get call translates name to ID). Use + // WithLogLines, WithLogSince, WithLogSources, and WithLogMinLevel to + // filter the results. + // + // Errors: NotFound if the sandbox does not exist; InvalidArgument if + // the sandbox name is empty; Unimplemented by the fake client. + GetLogs(ctx context.Context, workspace, sandboxName string, opts ...LogOption) (*LogResult, error) +} diff --git a/sdk/go/openshell/v1/sandbox_client.go b/sdk/go/openshell/v1/sandbox_client.go new file mode 100644 index 000000000..6c38db781 --- /dev/null +++ b/sdk/go/openshell/v1/sandbox_client.go @@ -0,0 +1,291 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + "fmt" + "io" + "time" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/internal/converter" + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" + pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1" + "google.golang.org/grpc" +) + +const defaultPollInterval = 500 * time.Millisecond + +type sandboxClient struct { + client pb.OpenShellClient +} + +func newSandboxClient(conn grpc.ClientConnInterface) *sandboxClient { + return &sandboxClient{client: pb.NewOpenShellClient(conn)} +} + +func (s *sandboxClient) Create(ctx context.Context, workspace, name string, spec *SandboxSpec, labels map[string]string) (*Sandbox, error) { + pbSpec, err := converter.SandboxSpecToProto(spec) + if err != nil { + return nil, &StatusError{Code: ErrorInvalidArgument, Message: err.Error()} + } + resp, err := s.client.CreateSandbox(ctx, &pb.CreateSandboxRequest{ + Name: name, + Spec: pbSpec, + Labels: labels, + Workspace: workspace, + }) + if err != nil { + return nil, converter.FromGRPCError(err) + } + return converter.SandboxFromProto(resp.GetSandbox()), nil +} + +func (s *sandboxClient) Get(ctx context.Context, workspace, name string) (*Sandbox, error) { + resp, err := s.client.GetSandbox(ctx, &pb.GetSandboxRequest{ + Name: name, + Workspace: workspace, + }) + if err != nil { + return nil, converter.FromGRPCError(err) + } + return converter.SandboxFromProto(resp.GetSandbox()), nil +} + +func (s *sandboxClient) List(ctx context.Context, workspace string, opts ...ListOptions) ([]*Sandbox, error) { + req := &pb.ListSandboxesRequest{ + Workspace: workspace, + } + if len(opts) > 0 { + if opts[0].Limit > 0 { + req.Limit = uint32(opts[0].Limit) + } + if opts[0].Offset > 0 { + req.Offset = uint32(opts[0].Offset) + } + req.LabelSelector = opts[0].LabelSelector + req.AllWorkspaces = opts[0].AllWorkspaces + } + + resp, err := s.client.ListSandboxes(ctx, req) + if err != nil { + return nil, converter.FromGRPCError(err) + } + + sandboxes := make([]*Sandbox, 0, len(resp.GetSandboxes())) + for _, proto := range resp.GetSandboxes() { + sandboxes = append(sandboxes, converter.SandboxFromProto(proto)) + } + return sandboxes, nil +} + +func (s *sandboxClient) Delete(ctx context.Context, workspace, name string) error { + _, err := s.client.DeleteSandbox(ctx, &pb.DeleteSandboxRequest{ + Name: name, + Workspace: workspace, + }) + if err != nil { + return converter.FromGRPCError(err) + } + return nil +} + +func (s *sandboxClient) AttachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*AttachProviderResult, error) { + resp, err := s.client.AttachSandboxProvider(ctx, &pb.AttachSandboxProviderRequest{ + SandboxName: sandboxName, + ProviderName: providerName, + ExpectedResourceVersion: expectedResourceVersion, + Workspace: workspace, + }) + if err != nil { + return nil, converter.FromGRPCError(err) + } + return &AttachProviderResult{ + Sandbox: converter.SandboxFromProto(resp.GetSandbox()), + Attached: resp.GetAttached(), + }, nil +} + +func (s *sandboxClient) DetachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*DetachProviderResult, error) { + resp, err := s.client.DetachSandboxProvider(ctx, &pb.DetachSandboxProviderRequest{ + SandboxName: sandboxName, + ProviderName: providerName, + ExpectedResourceVersion: expectedResourceVersion, + Workspace: workspace, + }) + if err != nil { + return nil, converter.FromGRPCError(err) + } + return &DetachProviderResult{ + Sandbox: converter.SandboxFromProto(resp.GetSandbox()), + Detached: resp.GetDetached(), + }, nil +} + +func (s *sandboxClient) ListProviders(ctx context.Context, workspace, sandboxName string) ([]*Provider, error) { + resp, err := s.client.ListSandboxProviders(ctx, &pb.ListSandboxProvidersRequest{ + SandboxName: sandboxName, + Workspace: workspace, + }) + if err != nil { + return nil, converter.FromGRPCError(err) + } + + providers := make([]*Provider, 0, len(resp.GetProviders())) + for _, proto := range resp.GetProviders() { + providers = append(providers, converter.ProviderFromProto(proto)) + } + return providers, nil +} + +func (s *sandboxClient) WaitReady(ctx context.Context, workspace, name string, opts ...WaitOptions) (*Sandbox, error) { + interval := defaultPollInterval + if len(opts) > 0 && opts[0].PollInterval > 0 { + interval = opts[0].PollInterval + } + + sb, err := s.Get(ctx, workspace, name) + if err != nil { + return nil, err + } + + if sb.Status.Phase == SandboxReady { + return sb, nil + } + if sb.Status.Phase == SandboxError { + return nil, &StatusError{Code: ErrorInternal, Message: fmt.Sprintf("sandbox %q is in error state", name)} + } + if sb.Status.Phase == SandboxDeleting { + return nil, &StatusError{Code: ErrorInternal, Message: fmt.Sprintf("sandbox %q is being deleted", name)} + } + + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return nil, contextError(ctx.Err()) + case <-ticker.C: + sb, err = s.Get(ctx, workspace, name) + if err != nil { + return nil, err + } + if sb.Status.Phase == SandboxReady { + return sb, nil + } + if sb.Status.Phase == SandboxError { + return nil, &StatusError{Code: ErrorInternal, Message: fmt.Sprintf("sandbox %q is in error state", name)} + } + if sb.Status.Phase == SandboxDeleting { + return nil, &StatusError{Code: ErrorInternal, Message: fmt.Sprintf("sandbox %q is being deleted", name)} + } + } + } +} + +func (s *sandboxClient) Watch(ctx context.Context, workspace, name string, opts ...WatchOptions) (WatchInterface[*Sandbox], error) { + if name == "" { + return nil, &StatusError{Code: ErrorInvalidArgument, Message: "sandbox name must not be empty"} + } + + var watchOpts WatchOptions + if len(opts) > 0 { + watchOpts = opts[0] + } + + // Resolve sandbox name to ID — the proto RPC takes Id, not name. + sb, err := s.Get(ctx, workspace, name) + if err != nil { + return nil, err + } + + streamCtx, streamCancel := context.WithCancel(ctx) + stream, err := s.client.WatchSandbox(streamCtx, &pb.WatchSandboxRequest{ + Id: sb.ID, + FollowStatus: true, + StopOnTerminal: watchOpts.StopOnTerminal, + }) + if err != nil { + streamCancel() + return nil, converter.FromGRPCError(err) + } + + first, err := stream.Recv() + if err != nil { + streamCancel() + return nil, converter.FromGRPCError(err) + } + + ch := make(chan Event[*Sandbox], 64) + w := newWatcher(ch, streamCancel) + + go func() { + defer close(ch) + defer streamCancel() + ev := first + isFirst := true + for { + if sbPayload, ok := ev.Payload.(*pb.SandboxStreamEvent_Sandbox); ok && sbPayload.Sandbox != nil { + sandbox := converter.SandboxFromProto(sbPayload.Sandbox) + eventType := EventModified + if isFirst { + eventType = EventAdded + isFirst = false + } else if sandbox.Status.Phase == SandboxDeleting { + eventType = EventDeleted + } + select { + case ch <- Event[*Sandbox]{Type: eventType, Object: sandbox}: + case <-w.done: + return + } + // StopOnTerminal: close watcher after delivering a terminal phase event + if watchOpts.StopOnTerminal && (sandbox.Status.Phase == SandboxReady || sandbox.Status.Phase == SandboxError) { + w.Stop() + return + } + } + var recvErr error + ev, recvErr = stream.Recv() + if recvErr != nil { + if recvErr != io.EOF { + select { + case ch <- Event[*Sandbox]{Type: EventError, Err: converter.FromGRPCError(recvErr)}: + case <-w.done: + } + } + return + } + } + }() + + return w, nil +} + +func (s *sandboxClient) GetLogs(ctx context.Context, workspace, sandboxName string, opts ...LogOption) (*LogResult, error) { + // Resolve sandbox name to ID — the proto RPC takes SandboxId, not name. + sb, err := s.Get(ctx, workspace, sandboxName) + if err != nil { + return nil, err + } + + cfg := types.ApplyLogOptions(opts) + req := &pb.GetSandboxLogsRequest{ + SandboxId: sb.ID, + Lines: cfg.Lines(), + Sources: cfg.Sources(), + MinLevel: cfg.MinLevel(), + Workspace: workspace, + } + if !cfg.Since().IsZero() { + req.SinceMs = converter.MillisFromTime(cfg.Since()) + } + + resp, err := s.client.GetSandboxLogs(ctx, req) + if err != nil { + return nil, converter.FromGRPCError(err) + } + return converter.LogResultFromProto(resp), nil +} diff --git a/sdk/go/openshell/v1/sandbox_client_test.go b/sdk/go/openshell/v1/sandbox_client_test.go new file mode 100644 index 000000000..2574348ec --- /dev/null +++ b/sdk/go/openshell/v1/sandbox_client_test.go @@ -0,0 +1,1068 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + "net" + "sync" + "testing" + "time" + + dm "github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1" + pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + "google.golang.org/protobuf/proto" +) + +const bufSize = 1024 * 1024 + +type mockSandboxServer struct { + pb.UnimplementedOpenShellServer + mu sync.Mutex + sandboxes map[string]*pb.Sandbox + providers map[string][]*dm.Provider + createErr error + getErr error + listErr error + deleteErr error + attachErr error + detachErr error + listProvErr error + watchEvents []*pb.SandboxStreamEvent + watchErr error + watchPostEventsErr error + watchKeepOpen chan struct{} // if non-nil, WatchSandbox blocks after sending events until closed + watchRequest *pb.WatchSandboxRequest // recorded request + + // GetLogs fields + getLogsResp *pb.GetSandboxLogsResponse + getLogsErr error + getLogsRequest *pb.GetSandboxLogsRequest // recorded request +} + +func newMockSandboxServer() *mockSandboxServer { + return &mockSandboxServer{ + sandboxes: make(map[string]*pb.Sandbox), + providers: make(map[string][]*dm.Provider), + } +} + +func (s *mockSandboxServer) CreateSandbox(_ context.Context, req *pb.CreateSandboxRequest) (*pb.SandboxResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + if s.createErr != nil { + return nil, s.createErr + } + sb := &pb.Sandbox{ + Metadata: &dm.ObjectMeta{ + Id: "sb-" + req.GetName(), + Name: req.GetName(), + CreatedAtMs: 1700000000000, + Labels: req.GetLabels(), + ResourceVersion: 1, + }, + Spec: req.GetSpec(), + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + } + s.sandboxes[req.GetName()] = sb + return &pb.SandboxResponse{Sandbox: sb}, nil +} + +func (s *mockSandboxServer) GetSandbox(_ context.Context, req *pb.GetSandboxRequest) (*pb.SandboxResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + if s.getErr != nil { + return nil, s.getErr + } + sb, ok := s.sandboxes[req.GetName()] + if !ok { + return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName()) + } + cloned := proto.Clone(sb).(*pb.Sandbox) + return &pb.SandboxResponse{Sandbox: cloned}, nil +} + +func (s *mockSandboxServer) setPhase(name string, phase pb.SandboxPhase) { + s.mu.Lock() + defer s.mu.Unlock() + if sb, ok := s.sandboxes[name]; ok { + sb.Status.Phase = phase + } +} + +func (s *mockSandboxServer) ListSandboxes(_ context.Context, _ *pb.ListSandboxesRequest) (*pb.ListSandboxesResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + if s.listErr != nil { + return nil, s.listErr + } + var list []*pb.Sandbox + for _, sb := range s.sandboxes { + list = append(list, sb) + } + return &pb.ListSandboxesResponse{Sandboxes: list}, nil +} + +func (s *mockSandboxServer) DeleteSandbox(_ context.Context, req *pb.DeleteSandboxRequest) (*pb.DeleteSandboxResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + if s.deleteErr != nil { + return nil, s.deleteErr + } + _, ok := s.sandboxes[req.GetName()] + if !ok { + return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName()) + } + delete(s.sandboxes, req.GetName()) + return &pb.DeleteSandboxResponse{Deleted: true}, nil +} + +func (s *mockSandboxServer) AttachSandboxProvider(_ context.Context, req *pb.AttachSandboxProviderRequest) (*pb.AttachSandboxProviderResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + if s.attachErr != nil { + return nil, s.attachErr + } + sb, ok := s.sandboxes[req.GetSandboxName()] + if !ok { + return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetSandboxName()) + } + sb.Spec.Providers = append(sb.Spec.Providers, req.GetProviderName()) + return &pb.AttachSandboxProviderResponse{Sandbox: sb, Attached: true}, nil +} + +func (s *mockSandboxServer) DetachSandboxProvider(_ context.Context, req *pb.DetachSandboxProviderRequest) (*pb.DetachSandboxProviderResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + if s.detachErr != nil { + return nil, s.detachErr + } + sb, ok := s.sandboxes[req.GetSandboxName()] + if !ok { + return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetSandboxName()) + } + return &pb.DetachSandboxProviderResponse{Sandbox: sb, Detached: true}, nil +} + +func (s *mockSandboxServer) ListSandboxProviders(_ context.Context, req *pb.ListSandboxProvidersRequest) (*pb.ListSandboxProvidersResponse, error) { + if s.listProvErr != nil { + return nil, s.listProvErr + } + provs := s.providers[req.GetSandboxName()] + return &pb.ListSandboxProvidersResponse{Providers: provs}, nil +} + +func (s *mockSandboxServer) WatchSandbox(req *pb.WatchSandboxRequest, stream grpc.ServerStreamingServer[pb.SandboxStreamEvent]) error { + s.mu.Lock() + s.watchRequest = req + s.mu.Unlock() + if s.watchErr != nil { + return s.watchErr + } + s.mu.Lock() + events := make([]*pb.SandboxStreamEvent, len(s.watchEvents)) + copy(events, s.watchEvents) + keepOpen := s.watchKeepOpen + s.mu.Unlock() + for _, ev := range events { + if err := stream.Send(ev); err != nil { + return err + } + } + if s.watchPostEventsErr != nil { + return s.watchPostEventsErr + } + // If watchKeepOpen is set, block until it is closed (simulates long-running stream) + if keepOpen != nil { + <-keepOpen + } + return nil +} + +func (s *mockSandboxServer) GetSandboxLogs(_ context.Context, req *pb.GetSandboxLogsRequest) (*pb.GetSandboxLogsResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.getLogsRequest = req + if s.getLogsErr != nil { + return nil, s.getLogsErr + } + if s.getLogsResp != nil { + return s.getLogsResp, nil + } + return &pb.GetSandboxLogsResponse{}, nil +} + +func setupSandboxTest(t *testing.T, mock *mockSandboxServer) (*sandboxClient, func()) { + t.Helper() + lis := bufconn.Listen(bufSize) + srv := grpc.NewServer() + pb.RegisterOpenShellServer(srv, mock) + go func() { _ = srv.Serve(lis) }() + + conn, err := grpc.NewClient("passthrough:///bufconn", + grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) { + return lis.Dial() + }), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + require.NoError(t, err) + + return newSandboxClient(conn), func() { + _ = conn.Close() + srv.Stop() + } +} + +// --- T029: Sandbox CRUD tests --- + +func TestSandboxCreate(t *testing.T) { + mock := newMockSandboxServer() + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + spec := &SandboxSpec{ + LogLevel: "debug", + Environment: map[string]string{"FOO": "bar"}, + Providers: []string{"claude"}, + } + labels := map[string]string{"env": "dev"} + + result, err := client.Create(context.Background(), "default", "my-sandbox", spec, labels) + + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, "my-sandbox", result.Name) + assert.Equal(t, "sb-my-sandbox", result.ID) + assert.Equal(t, map[string]string{"env": "dev"}, result.Labels) + assert.Equal(t, SandboxProvisioning, result.Status.Phase) +} + +func TestSandboxCreate_AlreadyExists(t *testing.T) { + mock := newMockSandboxServer() + mock.createErr = status.Error(codes.AlreadyExists, "sandbox already exists") + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + _, err := client.Create(context.Background(), "default", "dup", &SandboxSpec{}, nil) + + require.Error(t, err) + assert.True(t, IsAlreadyExists(err)) +} + +func TestSandboxGet(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["existing"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "sb-1", Name: "existing", ResourceVersion: 5}, + Spec: &pb.SandboxSpec{LogLevel: "info"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + result, err := client.Get(context.Background(), "default", "existing") + + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, "existing", result.Name) + assert.Equal(t, "sb-1", result.ID) + assert.Equal(t, uint64(5), result.ResourceVersion) + assert.Equal(t, SandboxReady, result.Status.Phase) +} + +func TestSandboxGet_NotFound(t *testing.T) { + mock := newMockSandboxServer() + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + _, err := client.Get(context.Background(), "default", "nonexistent") + + require.Error(t, err) + assert.True(t, IsNotFound(err)) +} + +func TestSandboxList(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["sb1"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + mock.sandboxes["sb2"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb2"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + result, err := client.List(context.Background(), "default") + + require.NoError(t, err) + assert.Len(t, result, 2) +} + +func TestSandboxList_Empty(t *testing.T) { + mock := newMockSandboxServer() + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + result, err := client.List(context.Background(), "default") + + require.NoError(t, err) + assert.Empty(t, result) +} + +func TestSandboxList_WithOptions(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["sb1"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + result, err := client.List(context.Background(), "default", ListOptions{Limit: 10, Offset: 0}) + + require.NoError(t, err) + assert.Len(t, result, 1) +} + +func TestSandboxDelete(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["deleteme"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "deleteme"}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + err := client.Delete(context.Background(), "default", "deleteme") + + require.NoError(t, err) + assert.Empty(t, mock.sandboxes["deleteme"]) +} + +func TestSandboxDelete_NotFound(t *testing.T) { + mock := newMockSandboxServer() + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + err := client.Delete(context.Background(), "default", "nonexistent") + + require.Error(t, err) + assert.True(t, IsNotFound(err)) +} + +// --- T030: AttachProvider, DetachProvider, ListProviders tests --- + +func TestSandboxAttachProvider(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["my-sb"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "my-sb", ResourceVersion: 2}, + Spec: &pb.SandboxSpec{Providers: []string{"existing-prov"}}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + result, err := client.AttachProvider(context.Background(), "default", "my-sb", "new-prov", 2) + + require.NoError(t, err) + require.NotNil(t, result) + assert.True(t, result.Attached) + require.NotNil(t, result.Sandbox) + assert.Equal(t, "my-sb", result.Sandbox.Name) +} + +func TestSandboxAttachProvider_NotFound(t *testing.T) { + mock := newMockSandboxServer() + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + _, err := client.AttachProvider(context.Background(), "default", "missing", "prov", 1) + + require.Error(t, err) + assert.True(t, IsNotFound(err)) +} + +func TestSandboxAttachProvider_Error(t *testing.T) { + mock := newMockSandboxServer() + mock.attachErr = status.Error(codes.InvalidArgument, "bad version") + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + _, err := client.AttachProvider(context.Background(), "default", "sb", "prov", 99) + + require.Error(t, err) + assert.True(t, IsInvalidArgument(err)) +} + +func TestSandboxDetachProvider(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["my-sb"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "my-sb", ResourceVersion: 3}, + Spec: &pb.SandboxSpec{Providers: []string{"prov-a", "prov-b"}}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + result, err := client.DetachProvider(context.Background(), "default", "my-sb", "prov-a", 3) + + require.NoError(t, err) + require.NotNil(t, result) + assert.True(t, result.Detached) + require.NotNil(t, result.Sandbox) + assert.Equal(t, "my-sb", result.Sandbox.Name) +} + +func TestSandboxDetachProvider_NotFound(t *testing.T) { + mock := newMockSandboxServer() + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + _, err := client.DetachProvider(context.Background(), "default", "missing", "prov", 1) + + require.Error(t, err) + assert.True(t, IsNotFound(err)) +} + +func TestSandboxListProviders(t *testing.T) { + mock := newMockSandboxServer() + mock.providers["my-sb"] = []*dm.Provider{ + {Metadata: &dm.ObjectMeta{Name: "claude-prov"}, Type: "claude"}, + {Metadata: &dm.ObjectMeta{Name: "github-prov"}, Type: "github"}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + result, err := client.ListProviders(context.Background(), "default", "my-sb") + + require.NoError(t, err) + assert.Len(t, result, 2) +} + +func TestSandboxListProviders_Empty(t *testing.T) { + mock := newMockSandboxServer() + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + result, err := client.ListProviders(context.Background(), "default", "empty-sb") + + require.NoError(t, err) + assert.Empty(t, result) +} + +func TestSandboxListProviders_Error(t *testing.T) { + mock := newMockSandboxServer() + mock.listProvErr = status.Error(codes.Unavailable, "service down") + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + _, err := client.ListProviders(context.Background(), "default", "sb") + + require.Error(t, err) + assert.True(t, IsUnavailable(err)) +} + +// --- T031: WaitReady tests --- + +func TestSandboxWaitReady_AlreadyReady(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["ready-sb"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "ready-sb"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + result, err := client.WaitReady(context.Background(), "default", "ready-sb") + + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, "ready-sb", result.Name) + assert.Equal(t, SandboxReady, result.Status.Phase) +} + +func TestSandboxWaitReady_BecomesReady(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["pending-sb"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "pending-sb"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + go func() { + time.Sleep(50 * time.Millisecond) + mock.setPhase("pending-sb", pb.SandboxPhase_SANDBOX_PHASE_READY) + }() + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + result, err := client.WaitReady(ctx, "default", "pending-sb", WaitOptions{PollInterval: 20 * time.Millisecond}) + + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, SandboxReady, result.Status.Phase) +} + +func TestSandboxWaitReady_ContextTimeout(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["stuck-sb"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "stuck-sb"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + + _, err := client.WaitReady(ctx, "default", "stuck-sb", WaitOptions{PollInterval: 20 * time.Millisecond}) + + require.Error(t, err) + assert.True(t, IsDeadlineExceeded(err), "WaitReady must wrap context.DeadlineExceeded in StatusError") +} + +func TestSandboxWaitReady_ContextCancelled(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["cancel-sb"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "cancel-sb"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(50 * time.Millisecond) + cancel() + }() + + _, err := client.WaitReady(ctx, "default", "cancel-sb", WaitOptions{PollInterval: 20 * time.Millisecond}) + + require.Error(t, err) + assert.True(t, IsCancelled(err), "WaitReady must wrap context.Canceled in StatusError") +} + +func TestSandboxWaitReady_SandboxFailed(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["fail-sb"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "fail-sb"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_ERROR}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + _, err := client.WaitReady(context.Background(), "default", "fail-sb") + + require.Error(t, err) +} + +func TestSandboxWaitReady_NotFound(t *testing.T) { + mock := newMockSandboxServer() + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + _, err := client.WaitReady(context.Background(), "default", "nonexistent") + + require.Error(t, err) + assert.True(t, IsNotFound(err)) +} + +// --- T039: Watch integration tests --- + +func TestSandboxWatch_ReceivesEvents(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["sb-1"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "id-1", Name: "sb-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + } + mock.watchEvents = []*pb.SandboxStreamEvent{ + {Payload: &pb.SandboxStreamEvent_Sandbox{Sandbox: &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb-1", Id: "id-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + }}}, + {Payload: &pb.SandboxStreamEvent_Sandbox{Sandbox: &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb-1", Id: "id-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + }}}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + w, err := client.Watch(context.Background(), "default", "sb-1") + require.NoError(t, err) + defer w.Stop() + + ev1 := <-w.ResultChan() + assert.Equal(t, EventAdded, ev1.Type) + require.NotNil(t, ev1.Object) + assert.Equal(t, "sb-1", ev1.Object.Name) + assert.Equal(t, SandboxProvisioning, ev1.Object.Status.Phase) + + ev2 := <-w.ResultChan() + assert.Equal(t, EventModified, ev2.Type) + assert.Equal(t, SandboxReady, ev2.Object.Status.Phase) +} + +func TestSandboxWatch_FiltersSandboxEventsOnly(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["sb-1"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "id-1", Name: "sb-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + mock.watchEvents = []*pb.SandboxStreamEvent{ + {Payload: &pb.SandboxStreamEvent_Log{Log: &pb.SandboxLogLine{Message: "some log"}}}, + {Payload: &pb.SandboxStreamEvent_Sandbox{Sandbox: &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + }}}, + {Payload: &pb.SandboxStreamEvent_Warning{Warning: &pb.SandboxStreamWarning{Message: "warn"}}}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + w, err := client.Watch(context.Background(), "default", "sb-1") + require.NoError(t, err) + defer w.Stop() + + ev := <-w.ResultChan() + assert.Equal(t, EventAdded, ev.Type) + assert.Equal(t, "sb-1", ev.Object.Name) + + // Stream ends after server sends all events; channel should close + select { + case _, ok := <-w.ResultChan(): + assert.False(t, ok, "channel should close after stream ends") + case <-time.After(time.Second): + t.Fatal("timed out waiting for channel close") + } +} + +func TestSandboxWatch_StopCancelsStream(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["sb-1"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "id-1", Name: "sb-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + mock.watchEvents = []*pb.SandboxStreamEvent{ + {Payload: &pb.SandboxStreamEvent_Sandbox{Sandbox: &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + }}}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + w, err := client.Watch(context.Background(), "default", "sb-1") + require.NoError(t, err) + + <-w.ResultChan() + w.Stop() + + select { + case _, ok := <-w.ResultChan(): + assert.False(t, ok, "channel should be closed after Stop") + case <-time.After(time.Second): + t.Fatal("timed out waiting for channel close after Stop") + } +} + +func TestSandboxWatch_RPCError(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["watch-err"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "id-watch-err", Name: "watch-err"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + mock.watchErr = status.Error(codes.Unavailable, "stream unavailable") + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + _, err := client.Watch(context.Background(), "default", "watch-err") + + require.Error(t, err) + assert.True(t, IsUnavailable(err)) +} + +func TestSandboxWatch_MidStreamErrorDeliveredAsStatusError(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["sb-1"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "id-1", Name: "sb-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + } + mock.watchEvents = []*pb.SandboxStreamEvent{ + {Payload: &pb.SandboxStreamEvent_Sandbox{Sandbox: &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb-1", Id: "id-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + }}}, + } + mock.watchPostEventsErr = status.Error(codes.Unavailable, "connection lost") + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + w, err := client.Watch(context.Background(), "default", "sb-1") + require.NoError(t, err) + defer w.Stop() + + ev1 := <-w.ResultChan() + assert.Equal(t, EventAdded, ev1.Type) + + ev2 := <-w.ResultChan() + assert.Equal(t, EventError, ev2.Type) + require.Error(t, ev2.Err) + assert.True(t, IsUnavailable(ev2.Err), "mid-stream error should be converted to StatusError") +} + +// --- T016: Watch name-to-ID resolution verification tests --- + +func TestSandboxWatch_ResolvesNameToID(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["my-sandbox"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "resolved-id-123", Name: "my-sandbox"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + } + mock.watchKeepOpen = make(chan struct{}) + defer close(mock.watchKeepOpen) + mock.watchEvents = []*pb.SandboxStreamEvent{ + {Payload: &pb.SandboxStreamEvent_Sandbox{Sandbox: &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "my-sandbox", Id: "resolved-id-123"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + }}}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + w, err := client.Watch(context.Background(), "default", "my-sandbox") + require.NoError(t, err) + defer w.Stop() + + // Verify the WatchSandboxRequest.Id contains the resolved ID, not the name + mock.mu.Lock() + req := mock.watchRequest + mock.mu.Unlock() + require.NotNil(t, req) + assert.Equal(t, "resolved-id-123", req.GetId(), "Watch should send resolved sandbox ID, not the name") +} + +func TestSandboxWatch_ResolutionError(t *testing.T) { + mock := newMockSandboxServer() + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + _, err := client.Watch(context.Background(), "default", "nonexistent") + + require.Error(t, err) + assert.True(t, IsNotFound(err), "Watch should return NotFound when sandbox name cannot be resolved") +} + +func TestSandboxWatch_EmptySandboxName(t *testing.T) { + mock := newMockSandboxServer() + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + _, err := client.Watch(context.Background(), "default", "") + + require.Error(t, err) + assert.True(t, IsInvalidArgument(err), "Watch should reject empty sandbox name") +} + +// --- T023/T024: StopOnTerminal watch tests --- + +func TestSandboxWatch_StopOnTerminal_Ready(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["sb-1"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "id-1", Name: "sb-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + } + mock.watchKeepOpen = make(chan struct{}) + defer close(mock.watchKeepOpen) + mock.watchEvents = []*pb.SandboxStreamEvent{ + {Payload: &pb.SandboxStreamEvent_Sandbox{Sandbox: &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb-1", Id: "id-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + }}}, + {Payload: &pb.SandboxStreamEvent_Sandbox{Sandbox: &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb-1", Id: "id-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + }}}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + w, err := client.Watch(context.Background(), "default", "sb-1", WatchOptions{StopOnTerminal: true}) + require.NoError(t, err) + defer w.Stop() + + // Should receive the Provisioning event + ev1 := <-w.ResultChan() + assert.Equal(t, EventAdded, ev1.Type) + assert.Equal(t, SandboxProvisioning, ev1.Object.Status.Phase) + + // Should receive the Ready event (terminal) + ev2 := <-w.ResultChan() + assert.Equal(t, EventModified, ev2.Type) + assert.Equal(t, SandboxReady, ev2.Object.Status.Phase) + + // Channel should close automatically after terminal event (stream is still open) + select { + case _, ok := <-w.ResultChan(): + assert.False(t, ok, "channel should close after terminal Ready event") + case <-time.After(time.Second): + t.Fatal("timed out waiting for channel close after terminal Ready event") + } +} + +func TestSandboxWatch_StopOnTerminal_Error(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["sb-1"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "id-1", Name: "sb-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + } + mock.watchKeepOpen = make(chan struct{}) + defer close(mock.watchKeepOpen) + mock.watchEvents = []*pb.SandboxStreamEvent{ + {Payload: &pb.SandboxStreamEvent_Sandbox{Sandbox: &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb-1", Id: "id-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + }}}, + {Payload: &pb.SandboxStreamEvent_Sandbox{Sandbox: &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb-1", Id: "id-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_ERROR}, + }}}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + w, err := client.Watch(context.Background(), "default", "sb-1", WatchOptions{StopOnTerminal: true}) + require.NoError(t, err) + defer w.Stop() + + // Should receive the Provisioning event + ev1 := <-w.ResultChan() + assert.Equal(t, EventAdded, ev1.Type) + assert.Equal(t, SandboxProvisioning, ev1.Object.Status.Phase) + + // Should receive the Error event (terminal) + ev2 := <-w.ResultChan() + assert.Equal(t, EventModified, ev2.Type) + assert.Equal(t, SandboxError, ev2.Object.Status.Phase) + + // Channel should close automatically after terminal event (stream is still open) + select { + case _, ok := <-w.ResultChan(): + assert.False(t, ok, "channel should close after terminal Error event") + case <-time.After(time.Second): + t.Fatal("timed out waiting for channel close after terminal Error event") + } +} + +func TestSandboxWatch_StopOnTerminal_False_DoesNotClose(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["sb-1"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "id-1", Name: "sb-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + mock.watchKeepOpen = make(chan struct{}) + defer close(mock.watchKeepOpen) + mock.watchEvents = []*pb.SandboxStreamEvent{ + {Payload: &pb.SandboxStreamEvent_Sandbox{Sandbox: &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb-1", Id: "id-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + }}}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + w, err := client.Watch(context.Background(), "default", "sb-1") + require.NoError(t, err) + defer w.Stop() + + ev := <-w.ResultChan() + assert.Equal(t, EventAdded, ev.Type) + assert.Equal(t, SandboxReady, ev.Object.Status.Phase) + + // Channel must NOT close: stream is still open and StopOnTerminal=false + select { + case <-w.ResultChan(): + t.Fatal("channel should stay open when StopOnTerminal is false") + case <-time.After(100 * time.Millisecond): + } +} + +func TestSandboxWatch_DeletedEvent(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["sb-1"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "id-1", Name: "sb-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + } + mock.watchEvents = []*pb.SandboxStreamEvent{ + {Payload: &pb.SandboxStreamEvent_Sandbox{Sandbox: &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb-1", Id: "id-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_PROVISIONING}, + }}}, + {Payload: &pb.SandboxStreamEvent_Sandbox{Sandbox: &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sb-1", Id: "id-1"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_DELETING}, + }}}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + w, err := client.Watch(context.Background(), "default", "sb-1") + require.NoError(t, err) + defer w.Stop() + + ev1 := <-w.ResultChan() + assert.Equal(t, EventAdded, ev1.Type) + + ev2 := <-w.ResultChan() + assert.Equal(t, EventDeleted, ev2.Type) + assert.Equal(t, SandboxDeleting, ev2.Object.Status.Phase) +} + +// --- T027: GetLogs tests --- + +func TestSandboxGetLogs(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["log-sb"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "sb-id-123", Name: "log-sb"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + mock.getLogsResp = &pb.GetSandboxLogsResponse{ + Logs: []*pb.SandboxLogLine{ + {TimestampMs: 1700000000000, Level: "INFO", Target: "gateway", Message: "connected", Source: "gateway"}, + {TimestampMs: 1700000001000, Level: "DEBUG", Target: "sandbox", Message: "init done", Source: "sandbox"}, + }, + BufferTotal: 42, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + result, err := client.GetLogs(context.Background(), "default", "log-sb") + + require.NoError(t, err) + require.NotNil(t, result) + assert.Len(t, result.Lines, 2) + assert.Equal(t, uint32(42), result.BufferTotal) + assert.Equal(t, "INFO", result.Lines[0].Level) + assert.Equal(t, "connected", result.Lines[0].Message) + assert.Equal(t, "gateway", result.Lines[0].Source) + assert.Equal(t, "DEBUG", result.Lines[1].Level) + assert.Equal(t, "init done", result.Lines[1].Message) + + // Verify name→id resolution: the proto request should contain the sandbox ID + mock.mu.Lock() + assert.Equal(t, "sb-id-123", mock.getLogsRequest.GetSandboxId()) + mock.mu.Unlock() +} + +func TestSandboxGetLogs_WithOptions(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["opts-sb"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "sb-id-opts", Name: "opts-sb"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + mock.getLogsResp = &pb.GetSandboxLogsResponse{ + Logs: []*pb.SandboxLogLine{{TimestampMs: 1700000000000, Level: "WARN", Message: "high cpu"}}, + BufferTotal: 100, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + since := time.Date(2023, 11, 14, 0, 0, 0, 0, time.UTC) + result, err := client.GetLogs(context.Background(), "default", "opts-sb", + WithLogLines(50), + WithLogSince(since), + WithLogSources("gateway", "sandbox"), + WithLogMinLevel("WARN"), + ) + + require.NoError(t, err) + require.NotNil(t, result) + assert.Len(t, result.Lines, 1) + assert.Equal(t, "WARN", result.Lines[0].Level) + + // Verify all options were passed to the proto request + mock.mu.Lock() + req := mock.getLogsRequest + mock.mu.Unlock() + assert.Equal(t, "sb-id-opts", req.GetSandboxId()) + assert.Equal(t, uint32(50), req.GetLines()) + assert.Equal(t, since.UnixMilli(), req.GetSinceMs()) + assert.Equal(t, []string{"gateway", "sandbox"}, req.GetSources()) + assert.Equal(t, "WARN", req.GetMinLevel()) +} + +func TestSandboxGetLogs_SandboxNotFound(t *testing.T) { + mock := newMockSandboxServer() + // No sandbox registered — Get will return NotFound + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + _, err := client.GetLogs(context.Background(), "default", "nonexistent") + + require.Error(t, err) + assert.True(t, IsNotFound(err)) +} + +func TestSandboxGetLogs_RPCError(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["rpc-err-sb"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "sb-id-rpc", Name: "rpc-err-sb"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + mock.getLogsErr = status.Error(codes.Unavailable, "log service down") + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + _, err := client.GetLogs(context.Background(), "default", "rpc-err-sb") + + require.Error(t, err) + assert.True(t, IsUnavailable(err)) +} + +func TestSandboxGetLogs_EmptyResult(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["empty-sb"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "sb-id-empty", Name: "empty-sb"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + mock.getLogsResp = &pb.GetSandboxLogsResponse{ + BufferTotal: 0, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + result, err := client.GetLogs(context.Background(), "default", "empty-sb") + + require.NoError(t, err) + require.NotNil(t, result) + assert.Empty(t, result.Lines) + assert.Equal(t, uint32(0), result.BufferTotal) +} + +func TestSandboxGetLogs_SinceZeroNotSent(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["zero-sb"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Id: "sb-id-zero", Name: "zero-sb"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + // Call without WithLogSince — SinceMs should be 0 (not set) + _, err := client.GetLogs(context.Background(), "default", "zero-sb") + + require.NoError(t, err) + mock.mu.Lock() + assert.Equal(t, int64(0), mock.getLogsRequest.GetSinceMs()) + mock.mu.Unlock() +} diff --git a/sdk/go/openshell/v1/service.go b/sdk/go/openshell/v1/service.go new file mode 100644 index 000000000..8d3f3c0f5 --- /dev/null +++ b/sdk/go/openshell/v1/service.go @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// ServiceEndpoint represents an exposed HTTP service endpoint within a sandbox. +type ServiceEndpoint = types.ServiceEndpoint + +// ServiceInterface defines operations for managing sandbox service endpoints. +type ServiceInterface interface { + // Expose creates a new service endpoint in the given sandbox. + Expose(ctx context.Context, workspace, sandboxName, serviceName string, targetPort uint32, domain bool) (*ServiceEndpoint, error) + // Get retrieves a service endpoint by sandbox and service name. + Get(ctx context.Context, workspace, sandboxName, serviceName string) (*ServiceEndpoint, error) + // List returns all service endpoints for a sandbox. An empty sandboxName returns endpoints across all sandboxes. + List(ctx context.Context, workspace, sandboxName string, opts ...ListOptions) ([]*ServiceEndpoint, error) + // Delete removes a service endpoint by sandbox and service name. + Delete(ctx context.Context, workspace, sandboxName, serviceName string) error +} diff --git a/sdk/go/openshell/v1/ssh.go b/sdk/go/openshell/v1/ssh.go new file mode 100644 index 000000000..de8d29b66 --- /dev/null +++ b/sdk/go/openshell/v1/ssh.go @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + "io" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// SSHSession represents an SSH session created for a sandbox. +type SSHSession = types.SSHSession + +// tunnelConfig accumulates options for the Tunnel method. +type tunnelConfig struct { + serviceID string +} + +// TunnelOption configures an SSH tunnel opened via [SSHInterface.Tunnel]. +type TunnelOption func(*tunnelConfig) + +// WithTunnelServiceID sets an optional service identifier on the tunnel's +// init frame for audit and correlation purposes. +func WithTunnelServiceID(id string) TunnelOption { + return func(c *tunnelConfig) { + c.serviceID = id + } +} + +// SSHInterface defines operations for managing SSH sessions. +type SSHInterface interface { + // CreateSession creates a new SSH session for the given sandbox. + // The returned SSHSession contains connection details including the + // sensitive Token field that must not be logged. + // + // Note: CreateSession accepts a raw sandbox ID, not a name. + // For name-based access with automatic session lifecycle management, + // prefer [SSHInterface.Tunnel] which resolves sandbox names internally + // and revokes the session on Close. + CreateSession(ctx context.Context, workspace, sandboxID string) (*SSHSession, error) + // RevokeSession revokes an existing SSH session by its token. + // Returns true if the session was actively revoked, false if it was + // already expired or not found. + RevokeSession(ctx context.Context, workspace, token string) (bool, error) + // Tunnel opens a bidirectional SSH tunnel to the given port inside a + // sandbox. It combines CreateSession and ForwardTcp(SshRelayTarget) + // into a single call with automatic session cleanup on Close. + // + // The sandboxName is resolved to a sandbox ID internally. Port must + // be in the range 1-65535. + // + // Errors: InvalidArgument if port is out of range or sandboxName is + // empty; NotFound if the sandbox does not exist; Unimplemented by + // the fake client; Unavailable if the client is closed. + Tunnel(ctx context.Context, workspace, sandboxName string, port uint32, opts ...TunnelOption) (io.ReadWriteCloser, error) +} diff --git a/sdk/go/openshell/v1/stub_clients.go b/sdk/go/openshell/v1/stub_clients.go new file mode 100644 index 000000000..1fb25a86a --- /dev/null +++ b/sdk/go/openshell/v1/stub_clients.go @@ -0,0 +1,195 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + "io" + "net" +) + +func stubError(method string) error { + return &StatusError{ + Code: ErrorUnimplemented, + Message: method + " not yet available - see https://github.com/NVIDIA/OpenShell/issues/2270", + } +} + +// stubExec implements ExecInterface as a placeholder. +type stubExec struct{} + +func (s *stubExec) Run(_ context.Context, _, _ string, _ []string, _ ...ExecOptions) (*ExecResult, error) { + return nil, stubError("Exec.Run") +} +func (s *stubExec) Stream(_ context.Context, _, _ string, _ []string, _ ...ExecOptions) (ExecStream, error) { + return nil, stubError("Exec.Stream") +} +func (s *stubExec) Interactive(_ context.Context, _, _ string, _ []string, _, _ uint32, _ ...ExecOptions) (InteractiveSession, error) { + return nil, stubError("Exec.Interactive") +} + +// stubFiles implements FileInterface as a placeholder. +type stubFiles struct{} + +func (s *stubFiles) Upload(_ context.Context, _, _, _, _ string) error { + return stubError("Files.Upload") +} +func (s *stubFiles) Download(_ context.Context, _, _, _, _ string) error { + return stubError("Files.Download") +} + +// stubHealth implements HealthInterface as a placeholder. +type stubHealth struct{} + +func (s *stubHealth) Check(_ context.Context) (*HealthResult, error) { + return nil, stubError("Health.Check") +} + +// stubProviders implements ProviderInterface as a placeholder. +type stubProviders struct{} + +func (s *stubProviders) Create(_ context.Context, _ string, _ *Provider) (*Provider, error) { + return nil, stubError("Providers.Create") +} +func (s *stubProviders) Get(_ context.Context, _, _ string) (*Provider, error) { + return nil, stubError("Providers.Get") +} +func (s *stubProviders) List(_ context.Context, _ string, _ ...ListOptions) ([]*Provider, error) { + return nil, stubError("Providers.List") +} +func (s *stubProviders) Update(_ context.Context, _ string, _ *Provider) (*Provider, error) { + return nil, stubError("Providers.Update") +} +func (s *stubProviders) Delete(_ context.Context, _, _ string) error { + return stubError("Providers.Delete") +} +func (s *stubProviders) Ensure(_ context.Context, _ string, _ *Provider) (*Provider, error) { + return nil, stubError("Providers.Ensure") +} +func (s *stubProviders) Profiles() ProfileInterface { return &stubProfiles{} } +func (s *stubProviders) Refresh() RefreshInterface { return &stubRefresh{} } + +// stubProfiles implements ProfileInterface as a placeholder. +type stubProfiles struct{} + +func (s *stubProfiles) List(_ context.Context, _ string, _ ...ListOptions) ([]*ProviderProfile, error) { + return nil, stubError("Profiles.List") +} +func (s *stubProfiles) Get(_ context.Context, _, _ string) (*ProviderProfile, error) { + return nil, stubError("Profiles.Get") +} +func (s *stubProfiles) Import(_ context.Context, _ string, _ []ProfileImportItem) (*ImportResult, error) { + return nil, stubError("Profiles.Import") +} +func (s *stubProfiles) Update(_ context.Context, _, _ string, _ uint64, _ ProfileImportItem) (*UpdateResult, error) { + return nil, stubError("Profiles.Update") +} +func (s *stubProfiles) Lint(_ context.Context, _ string, _ []ProfileImportItem) (*LintResult, error) { + return nil, stubError("Profiles.Lint") +} +func (s *stubProfiles) Delete(_ context.Context, _, _ string) (bool, error) { + return false, stubError("Profiles.Delete") +} + +// stubRefresh implements RefreshInterface as a placeholder. +type stubRefresh struct{} + +func (s *stubRefresh) GetStatus(_ context.Context, _, _, _ string) ([]*RefreshStatus, error) { + return nil, stubError("Refresh.GetStatus") +} +func (s *stubRefresh) Configure(_ context.Context, _ string, _ *RefreshConfig) (*RefreshStatus, error) { + return nil, stubError("Refresh.Configure") +} +func (s *stubRefresh) Rotate(_ context.Context, _, _, _ string) (*RefreshStatus, error) { + return nil, stubError("Refresh.Rotate") +} +func (s *stubRefresh) Delete(_ context.Context, _, _, _ string) (bool, error) { + return false, stubError("Refresh.Delete") +} + +// stubServices implements ServiceInterface as a placeholder. +type stubServices struct{} + +func (s *stubServices) Expose(_ context.Context, _, _, _ string, _ uint32, _ bool) (*ServiceEndpoint, error) { + return nil, stubError("Services.Expose") +} +func (s *stubServices) Get(_ context.Context, _, _, _ string) (*ServiceEndpoint, error) { + return nil, stubError("Services.Get") +} +func (s *stubServices) List(_ context.Context, _, _ string, _ ...ListOptions) ([]*ServiceEndpoint, error) { + return nil, stubError("Services.List") +} +func (s *stubServices) Delete(_ context.Context, _, _, _ string) error { + return stubError("Services.Delete") +} + +// stubSSH implements SSHInterface as a placeholder. +type stubSSH struct{} + +func (s *stubSSH) CreateSession(_ context.Context, _, _ string) (*SSHSession, error) { + return nil, stubError("SSH.CreateSession") +} +func (s *stubSSH) RevokeSession(_ context.Context, _, _ string) (bool, error) { + return false, stubError("SSH.RevokeSession") +} +func (s *stubSSH) Tunnel(_ context.Context, _, _ string, _ uint32, _ ...TunnelOption) (io.ReadWriteCloser, error) { + return nil, stubError("SSH.Tunnel") +} + +// stubTCP implements TCPInterface as a placeholder. +type stubTCP struct{} + +func (s *stubTCP) Forward(_ context.Context, _, _ string, _ uint32, _ ...ForwardOption) (io.ReadWriteCloser, error) { + return nil, stubError("TCP.Forward") +} +func (s *stubTCP) Listen(_ context.Context, _, _ string, _, _ uint32, _ ...ListenOption) (net.Listener, error) { + return nil, stubError("TCP.Listen") +} + +// stubConfig implements ConfigInterface as a placeholder. +type stubConfig struct{} + +func (s *stubConfig) GetSandbox(_ context.Context, _, _ string) (*SandboxConfig, error) { + return nil, stubError("Config.GetSandbox") +} +func (s *stubConfig) GetGateway(_ context.Context) (*GatewayConfig, error) { + return nil, stubError("Config.GetGateway") +} +func (s *stubConfig) Update(_ context.Context, _ string, _ *ConfigUpdate) (*ConfigUpdateResult, error) { + return nil, stubError("Config.Update") +} + +// stubPolicy implements PolicyInterface as a placeholder. +type stubPolicy struct{} + +func (s *stubPolicy) GetDraft(_ context.Context, _, _ string, _ ...GetDraftOption) (*DraftPolicy, error) { + return nil, stubError("Policy.GetDraft") +} +func (s *stubPolicy) ApproveDraftChunk(_ context.Context, _, _, _ string) (*ApproveResult, error) { + return nil, stubError("Policy.ApproveDraftChunk") +} +func (s *stubPolicy) RejectDraftChunk(_ context.Context, _, _, _, _ string) error { + return stubError("Policy.RejectDraftChunk") +} +func (s *stubPolicy) ApproveAllDraftChunks(_ context.Context, _, _ string, _ ...ApproveAllOption) (*ApproveAllResult, error) { + return nil, stubError("Policy.ApproveAllDraftChunks") +} +func (s *stubPolicy) ClearDraftChunks(_ context.Context, _, _ string) (*ClearResult, error) { + return nil, stubError("Policy.ClearDraftChunks") +} +func (s *stubPolicy) GetDraftHistory(_ context.Context, _, _ string) ([]DraftHistoryEntry, error) { + return nil, stubError("Policy.GetDraftHistory") +} +func (s *stubPolicy) GetStatus(_ context.Context, _, _ string, _ ...GetStatusOption) (*PolicyStatusResult, error) { + return nil, stubError("Policy.GetStatus") +} +func (s *stubPolicy) List(_ context.Context, _ string, _ ...ListPolicyOption) ([]SandboxPolicyRevision, error) { + return nil, stubError("Policy.List") +} +func (s *stubPolicy) EditDraftChunk(_ context.Context, _, _, _ string, _ *NetworkPolicyRule) error { + return stubError("Policy.EditDraftChunk") +} +func (s *stubPolicy) UndoDraftChunk(_ context.Context, _, _, _ string) (*UndoResult, error) { + return nil, stubError("Policy.UndoDraftChunk") +} diff --git a/sdk/go/openshell/v1/tcp.go b/sdk/go/openshell/v1/tcp.go new file mode 100644 index 000000000..950d2e206 --- /dev/null +++ b/sdk/go/openshell/v1/tcp.go @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + "io" + "net" +) + +// forwardConfig accumulates options for the Forward method. +type forwardConfig struct { + serviceID string +} + +// ForwardOption configures a TCP forward opened via [TCPInterface.Forward]. +type ForwardOption func(*forwardConfig) + +// WithForwardServiceID sets an optional service identifier on the forward's +// init frame for audit and correlation purposes. +func WithForwardServiceID(id string) ForwardOption { + return func(c *forwardConfig) { + c.serviceID = id + } +} + +// listenConfig accumulates options for the Listen method. +type listenConfig struct { + bindAddress string + useSSHTunnel bool + serviceID string +} + +// ListenOption configures a local listener opened via [TCPInterface.Listen]. +type ListenOption func(*listenConfig) + +// WithBindAddress overrides the default local bind address ("127.0.0.1"). +// Pass "0.0.0.0" to accept connections from any interface. +func WithBindAddress(addr string) ListenOption { + return func(c *listenConfig) { + c.bindAddress = addr + } +} + +// WithSSHTunnel routes each accepted connection through an SSH tunnel +// ([SSHInterface.Tunnel]) instead of the default TCP forward +// ([TCPInterface.Forward]). +func WithSSHTunnel() ListenOption { + return func(c *listenConfig) { + c.useSSHTunnel = true + } +} + +// WithListenServiceID sets an optional service identifier on each tunneled +// connection's init frame for audit and correlation purposes. +func WithListenServiceID(id string) ListenOption { + return func(c *listenConfig) { + c.serviceID = id + } +} + +// TCPInterface defines operations for TCP port forwarding to sandboxes. +// Methods accept a sandbox name and resolve it to an ID internally. +type TCPInterface interface { + // Forward opens a bidirectional TCP connection to the given port inside a + // sandbox. The sandbox is identified by name; the SDK resolves it to an + // ID internally. The returned io.ReadWriteCloser wraps the underlying + // gRPC stream; closing it terminates the stream. Port must be in the + // range 1-65535; out-of-range values are rejected client-side with an + // InvalidArgument error before opening the gRPC stream. + // + // The connection respects context cancellation: if ctx is cancelled, + // the stream is closed and pending Read/Write calls return a context error. + Forward(ctx context.Context, workspace, sandboxName string, port uint32, opts ...ForwardOption) (io.ReadWriteCloser, error) + + // Listen binds a local TCP port and tunnels every accepted connection to + // the given port inside a sandbox, returning a standard [net.Listener]. + // Each call to Accept on the returned listener establishes a new tunnel + // to the sandbox port, bridging data bidirectionally. + // + // The sandbox is identified by name; the SDK resolves it to an ID + // internally. remotePort must be in the range 1-65535; localPort must be + // in the range 0-65535, where 0 lets the OS assign an ephemeral port + // (discoverable via Addr). + // + // Closing the listener stops accepting new connections, tears down all + // active tunnels, and blocks until all bridge goroutines finish. + // Cancelling ctx triggers the same shutdown behavior. + // + // Errors: + // - InvalidArgument: sandboxName is empty, remotePort is 0 or > 65535, + // or localPort is > 65535 + // - Unimplemented: returned by the fake client + // - Unavailable: client is closed + Listen(ctx context.Context, workspace, sandboxName string, remotePort uint32, localPort uint32, opts ...ListenOption) (net.Listener, error) +} diff --git a/sdk/go/openshell/v1/types.go b/sdk/go/openshell/v1/types.go new file mode 100644 index 000000000..012811cab --- /dev/null +++ b/sdk/go/openshell/v1/types.go @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// SandboxPhase represents the lifecycle phase of a sandbox. +type SandboxPhase = types.SandboxPhase + +// SandboxPhase values for sandbox lifecycle. +const ( + SandboxProvisioning = types.SandboxProvisioning + SandboxReady = types.SandboxReady + SandboxError = types.SandboxError + SandboxDeleting = types.SandboxDeleting + SandboxUnknown = types.SandboxUnknown +) + +// EventType classifies watch events. +type EventType = types.EventType + +// EventType values for watch events. +const ( + EventAdded = types.EventAdded + EventModified = types.EventModified + EventDeleted = types.EventDeleted + EventError = types.EventError +) + +// StreamType identifies which output stream a chunk belongs to. +type StreamType = types.StreamType + +// StreamType values for exec output. +const ( + StreamStdout = types.StreamStdout + StreamStderr = types.StreamStderr +) + +// TLSConfig holds TLS connection settings. +type TLSConfig = types.TLSConfig + +// RetryPolicy configures automatic retry behavior for failed RPCs. +type RetryPolicy = types.RetryPolicy diff --git a/sdk/go/openshell/v1/types/auth.go b/sdk/go/openshell/v1/types/auth.go new file mode 100644 index 000000000..90da22473 --- /dev/null +++ b/sdk/go/openshell/v1/types/auth.go @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import "context" + +// AuthProvider supplies per-RPC credentials. It implements the +// grpc credentials.PerRPCCredentials interface. +type AuthProvider interface { + GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) + RequireTransportSecurity() bool +} diff --git a/sdk/go/openshell/v1/types/config.go b/sdk/go/openshell/v1/types/config.go new file mode 100644 index 000000000..9657061ff --- /dev/null +++ b/sdk/go/openshell/v1/types/config.go @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import "time" + +// Config holds all settings needed to create a Client. +type Config struct { + Address string + TLS *TLSConfig + Auth AuthProvider + // Timeout is reserved for future use. It is not yet applied. + Timeout time.Duration + // RetryPolicy is reserved for future use. It is not yet applied. + RetryPolicy *RetryPolicy + // Logger is reserved for future use. It is not yet applied. + Logger Logger +} diff --git a/sdk/go/openshell/v1/types/doc.go b/sdk/go/openshell/v1/types/doc.go new file mode 100644 index 000000000..c6577baf4 --- /dev/null +++ b/sdk/go/openshell/v1/types/doc.go @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Package types defines all domain data types for the OpenShell SDK v1 API. +// +// These types are the canonical definitions used by both the client layer +// (openshell/v1) and the converter layer (openshell/v1/internal/converter). +// The v1 package re-exports all types via type aliases for backward +// compatibility. +package types diff --git a/sdk/go/openshell/v1/types/errors.go b/sdk/go/openshell/v1/types/errors.go new file mode 100644 index 000000000..14d43cd75 --- /dev/null +++ b/sdk/go/openshell/v1/types/errors.go @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "errors" + "fmt" +) + +// ErrorCode classifies SDK errors by their gRPC origin. +type ErrorCode int + +// ErrorCode values for classifying gRPC errors. +const ( + ErrorNotFound ErrorCode = iota + 1 + ErrorAlreadyExists + ErrorUnavailable + ErrorPermissionDenied + ErrorInvalidArgument + ErrorDeadlineExceeded + ErrorCancelled + ErrorInternal + ErrorUnimplemented + ErrorConflict + ErrorUnauthenticated +) + +// String returns the human-readable name of the error code. +func (c ErrorCode) String() string { + switch c { + case ErrorNotFound: + return "NotFound" + case ErrorAlreadyExists: + return "AlreadyExists" + case ErrorUnavailable: + return "Unavailable" + case ErrorPermissionDenied: + return "PermissionDenied" + case ErrorInvalidArgument: + return "InvalidArgument" + case ErrorDeadlineExceeded: + return "DeadlineExceeded" + case ErrorCancelled: + return "Cancelled" + case ErrorInternal: + return "Internal" + case ErrorUnimplemented: + return "Unimplemented" + case ErrorConflict: + return "Conflict" + case ErrorUnauthenticated: + return "Unauthenticated" + default: + return fmt.Sprintf("Unknown(%d)", int(c)) + } +} + +// StatusError is the typed error returned by all SDK operations. +type StatusError struct { + Code ErrorCode + Message string + Cause error +} + +func (e *StatusError) Error() string { + return fmt.Sprintf("%s: %s", e.Code, e.Message) +} + +func (e *StatusError) Unwrap() error { + return e.Cause +} + +// IsNotFound returns true if the error indicates a resource was not found. +func IsNotFound(err error) bool { + return hasCode(err, ErrorNotFound) +} + +// IsAlreadyExists returns true if the error indicates a resource already exists. +func IsAlreadyExists(err error) bool { + return hasCode(err, ErrorAlreadyExists) +} + +// IsUnavailable returns true if the error indicates the service is unavailable. +func IsUnavailable(err error) bool { + return hasCode(err, ErrorUnavailable) +} + +// IsPermissionDenied returns true if the error indicates insufficient permissions. +func IsPermissionDenied(err error) bool { + return hasCode(err, ErrorPermissionDenied) +} + +// IsInvalidArgument returns true if the error indicates an invalid argument. +func IsInvalidArgument(err error) bool { + return hasCode(err, ErrorInvalidArgument) +} + +// IsDeadlineExceeded returns true if the error indicates a deadline was exceeded. +func IsDeadlineExceeded(err error) bool { + return hasCode(err, ErrorDeadlineExceeded) +} + +// IsCancelled returns true if the error indicates the operation was cancelled. +func IsCancelled(err error) bool { + return hasCode(err, ErrorCancelled) +} + +// IsUnimplemented returns true if the error indicates the operation is not implemented. +func IsUnimplemented(err error) bool { + return hasCode(err, ErrorUnimplemented) +} + +// IsConflict returns true if the error indicates a conflict, such as +// optimistic concurrency or an invalid state transition. +func IsConflict(err error) bool { + return hasCode(err, ErrorConflict) +} + +// IsUnauthenticated returns true if the error indicates missing or invalid credentials. +func IsUnauthenticated(err error) bool { + return hasCode(err, ErrorUnauthenticated) +} + +func hasCode(err error, code ErrorCode) bool { + if err == nil { + return false + } + var se *StatusError + if errors.As(err, &se) { + return se.Code == code + } + return false +} diff --git a/sdk/go/openshell/v1/types/exec.go b/sdk/go/openshell/v1/types/exec.go new file mode 100644 index 000000000..ecc322a2d --- /dev/null +++ b/sdk/go/openshell/v1/types/exec.go @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +// ExecResult holds the collected output of a completed command execution. +type ExecResult struct { + ExitCode int + Stdout []byte + Stderr []byte +} + +// ExecChunk represents a single chunk of output from a streaming command execution. +type ExecChunk struct { + Stream StreamType + Data []byte +} diff --git a/sdk/go/openshell/v1/types/health.go b/sdk/go/openshell/v1/types/health.go new file mode 100644 index 000000000..003618318 --- /dev/null +++ b/sdk/go/openshell/v1/types/health.go @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +// HealthResult holds the result of a health check. +type HealthResult struct { + Healthy bool + Version string +} diff --git a/sdk/go/openshell/v1/types/log.go b/sdk/go/openshell/v1/types/log.go new file mode 100644 index 000000000..85a62c29d --- /dev/null +++ b/sdk/go/openshell/v1/types/log.go @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import "time" + +// LogLine represents a single log entry from a sandbox. +type LogLine struct { + // Timestamp is when the log entry was recorded. + Timestamp time.Time + // Level is the log severity level (e.g., "INFO", "WARN", "ERROR"). + Level string + // Target is the log target/module. + Target string + // Message is the log message text. + Message string + // Source is the log source: "gateway" or "sandbox". + Source string + // Fields contains structured key-value fields from the tracing event. + Fields map[string]string +} + +// LogResult contains the result of a GetLogs call. +type LogResult struct { + // Lines contains the log entries in chronological order. + Lines []LogLine + // BufferTotal is the total number of lines in the server's buffer. + BufferTotal uint32 +} + +// logConfig holds configuration for GetLogs calls. +type logConfig struct { + lines uint32 + since time.Time + sources []string + minLevel string +} + +// LogOption configures a GetLogs call. +type LogOption func(*logConfig) + +// WithLogLines sets the maximum number of log lines to return. +func WithLogLines(n uint32) LogOption { + return func(c *logConfig) { + c.lines = n + } +} + +// WithLogSince filters logs to entries at or after the given time. +func WithLogSince(t time.Time) LogOption { + return func(c *logConfig) { + c.since = t + } +} + +// WithLogSources filters logs by source (e.g., "gateway", "sandbox"). +func WithLogSources(sources ...string) LogOption { + return func(c *logConfig) { + c.sources = sources + } +} + +// WithLogMinLevel sets the minimum log level to include. +func WithLogMinLevel(level string) LogOption { + return func(c *logConfig) { + c.minLevel = level + } +} + +// ApplyLogOptions applies options and returns the config. +func ApplyLogOptions(opts []LogOption) logConfig { //nolint:revive // unexported return is intentional; consumed only by v1 package + var cfg logConfig + for _, opt := range opts { + opt(&cfg) + } + return cfg +} + +// Lines returns the configured max lines (0 means server default). +func (c *logConfig) Lines() uint32 { + return c.lines +} + +// Since returns the configured since timestamp (zero means no filter). +func (c *logConfig) Since() time.Time { + return c.since +} + +// Sources returns the configured source filters. +func (c *logConfig) Sources() []string { + return c.sources +} + +// MinLevel returns the configured minimum log level. +func (c *logConfig) MinLevel() string { + return c.minLevel +} diff --git a/sdk/go/openshell/v1/types/logger.go b/sdk/go/openshell/v1/types/logger.go new file mode 100644 index 000000000..351630cb0 --- /dev/null +++ b/sdk/go/openshell/v1/types/logger.go @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +// Logger defines structured logging for the SDK. Compatible with logr.Logger +// and slog.Logger adapters. +type Logger interface { + Debug(msg string, keysAndValues ...any) + Info(msg string, keysAndValues ...any) + Error(err error, msg string, keysAndValues ...any) +} diff --git a/sdk/go/openshell/v1/types/network_policy.go b/sdk/go/openshell/v1/types/network_policy.go new file mode 100644 index 000000000..334a47574 --- /dev/null +++ b/sdk/go/openshell/v1/types/network_policy.go @@ -0,0 +1,170 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +// NetworkPolicyRule defines a named network policy rule containing endpoints and binaries. +type NetworkPolicyRule struct { + // Name is the map key for this rule in the sandbox policy. + Name string + // Endpoints lists the network endpoints governed by this rule. + Endpoints []PolicyNetworkEndpoint + // Binaries lists the binaries governed by this rule. + Binaries []PolicyNetworkBinary +} + +// PolicyNetworkEndpoint describes a full network endpoint with its access controls +// as used in sandbox network policy rules. This is distinct from [NetworkEndpoint] +// which is the simplified profile-level endpoint (Host, Port, Protocol only). +type PolicyNetworkEndpoint struct { + Host string + Port uint32 + Ports []uint32 + Protocol string + TLS string + Enforcement string + Access string + Rules []L7Rule + AllowedIPs []string + DenyRules []L7DenyRule + AllowEncodedSlash bool + PersistedQueries string + GraphqlPersistedQueries map[string]GraphqlOperation + GraphqlMaxBodyBytes uint32 + Path string + WebsocketCredentialRewrite bool + RequestBodyCredentialRewrite bool + AdvisorProposed bool + CredentialSigning string + SigningService string + SigningRegion string + JsonRpcMaxBodyBytes uint32 + Mcp *McpOptions +} + +// PolicyNetworkBinary identifies a binary subject to network policy enforcement. +// This is distinct from [NetworkBinary] which is the simplified profile-level binary. +type PolicyNetworkBinary struct { + // Path is the filesystem path to the binary. + Path string +} + +// L7Rule wraps an L7 allow rule. +type L7Rule struct { + // Allow holds the layer-7 allow criteria. + Allow *L7Allow +} + +// L7Allow specifies layer-7 allow criteria for HTTP/GraphQL/MCP traffic. +type L7Allow struct { + Method string + Path string + Command string + Query map[string]L7QueryMatcher + OperationType string + OperationName string + Fields []string + Params map[string]L7QueryMatcher +} + +// L7DenyRule specifies layer-7 deny criteria for HTTP/GraphQL/MCP traffic. +type L7DenyRule struct { + Method string + Path string + Command string + Query map[string]L7QueryMatcher + OperationType string + OperationName string + Fields []string + Params map[string]L7QueryMatcher +} + +// McpOptions holds MCP-specific policy and inspection options. +type McpOptions struct { + StrictToolNames *bool + AllowAllKnownMcpMethods *bool +} + +// L7QueryMatcher matches query parameters by glob pattern or exact values. +type L7QueryMatcher struct { + Glob string + Any []string +} + +// GraphqlOperation describes a GraphQL operation for persisted-query validation. +type GraphqlOperation struct { + OperationType string + OperationName string + Fields []string +} + +// --- MergeOperation types --- + +// PolicyMergeOperation represents a single atomic policy mutation. +// Exactly one of the pointer fields must be non-nil, modelling the proto oneof. +type PolicyMergeOperation struct { + // AddRule adds a new named network policy rule. + AddRule *AddNetworkRule + // RemoveEndpoint removes a single endpoint from a rule. + RemoveEndpoint *RemoveNetworkEndpoint + // RemoveRule removes an entire named rule. + RemoveRule *RemoveNetworkRule + // AddDenyRules appends deny rules to an endpoint. + AddDenyRules *AddDenyRules + // AddAllowRules appends allow rules to an endpoint. + AddAllowRules *AddAllowRules + // RemoveBinary removes a binary from a rule. + RemoveBinary *RemoveNetworkBinary +} + +// AddNetworkRule adds a named network policy rule with a full rule definition. +type AddNetworkRule struct { + // RuleName is the name key for the rule. + RuleName string + // Rule is the full network policy rule to add. + Rule NetworkPolicyRule +} + +// RemoveNetworkEndpoint removes a specific endpoint from a named rule. +type RemoveNetworkEndpoint struct { + // RuleName is the name of the rule containing the endpoint. + RuleName string + // Host is the endpoint host to remove. + Host string + // Port is the endpoint port to remove. + Port uint32 +} + +// RemoveNetworkRule removes an entire named rule from the policy. +type RemoveNetworkRule struct { + // RuleName is the name of the rule to remove. + RuleName string +} + +// AddDenyRules appends layer-7 deny rules to a specific endpoint. +type AddDenyRules struct { + // Host identifies the target endpoint host. + Host string + // Port identifies the target endpoint port. + Port uint32 + // DenyRules are the deny rules to append. + DenyRules []L7DenyRule +} + +// AddAllowRules appends layer-7 allow rules to a specific endpoint. +type AddAllowRules struct { + // Host identifies the target endpoint host. + Host string + // Port identifies the target endpoint port. + Port uint32 + // Rules are the allow rules to append. + Rules []L7Rule +} + +// RemoveNetworkBinary removes a binary from a named rule. +type RemoveNetworkBinary struct { + // RuleName is the name of the rule containing the binary. + RuleName string + // BinaryPath is the filesystem path of the binary to remove. + BinaryPath string +} diff --git a/sdk/go/openshell/v1/types/options.go b/sdk/go/openshell/v1/types/options.go new file mode 100644 index 000000000..4454b383b --- /dev/null +++ b/sdk/go/openshell/v1/types/options.go @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import "time" + +// CreateOptions configures resource creation. +type CreateOptions struct{} + +// GetOptions configures resource retrieval. +type GetOptions struct{} + +// ListOptions configures resource listing with pagination and filtering. +type ListOptions struct { + Limit int + Offset int + LabelSelector string + AllWorkspaces bool +} + +// DeleteOptions configures resource deletion. +type DeleteOptions struct{} + +// UpdateOptions configures resource updates. +type UpdateOptions struct{} + +// WatchOptions configures watch behavior. +type WatchOptions struct { + // TimeoutSeconds is reserved for future use. Use context for timeout control. + TimeoutSeconds int64 + // LabelSelector is reserved for future use. + LabelSelector string + // StopOnTerminal causes the watch to close automatically when the sandbox + // reaches a terminal phase (Ready or Error). + StopOnTerminal bool +} + +// WaitOptions configures wait behavior. Use context for timeout control. +type WaitOptions struct { + PollInterval time.Duration +} + +// ExecOptions configures command execution. +type ExecOptions struct { + Env map[string]string + WorkDir string +} diff --git a/sdk/go/openshell/v1/types/policy.go b/sdk/go/openshell/v1/types/policy.go new file mode 100644 index 000000000..f71aeca1d --- /dev/null +++ b/sdk/go/openshell/v1/types/policy.go @@ -0,0 +1,341 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import "time" + +// PolicyLoadStatus represents the load state of a policy revision. +type PolicyLoadStatus int + +const ( + // PolicyLoadStatusUnspecified is the default zero value. + PolicyLoadStatusUnspecified PolicyLoadStatus = iota + // PolicyLoadStatusPending means the policy is queued for loading. + PolicyLoadStatusPending + // PolicyLoadStatusLoaded means the policy was successfully loaded. + PolicyLoadStatusLoaded + // PolicyLoadStatusFailed means the policy failed to load. + PolicyLoadStatusFailed + // PolicyLoadStatusSuperseded means a newer revision replaced this one. + PolicyLoadStatusSuperseded +) + +// String returns the human-readable name of the load status. +func (s PolicyLoadStatus) String() string { + switch s { + case PolicyLoadStatusUnspecified: + return "Unspecified" + case PolicyLoadStatusPending: + return "Pending" + case PolicyLoadStatusLoaded: + return "Loaded" + case PolicyLoadStatusFailed: + return "Failed" + case PolicyLoadStatusSuperseded: + return "Superseded" + default: + return "Unknown" + } +} + +// PolicyChunk represents a single proposed policy change in the draft inbox. +type PolicyChunk struct { + // ID is the unique chunk identifier. + ID string + // Status is the approval status: "pending", "approved", "rejected". + Status string + // RuleName is the proposed network_policies map key. + RuleName string + // ProposedRule is the proposed network policy rule. + ProposedRule *NetworkPolicyRule + + // Rationale is a human-readable explanation of why this rule is proposed. + Rationale string + // SecurityNotes contains security concerns flagged by analysis (empty if none). + SecurityNotes string + // Confidence is the analysis confidence score (0.0-1.0). + Confidence float32 + // DenialSummaryIDs lists the IDs of denial summaries that led to this chunk. + DenialSummaryIDs []string + // CreatedAt is when the chunk was created. + CreatedAt time.Time + // DecidedAt is when the user approved/rejected (zero if undecided). + DecidedAt time.Time + // Stage is the recommendation stage: "initial" or "refined". + Stage string + // SupersedesChunkID is the initial chunk ID this refined chunk replaces. + SupersedesChunkID string + // HitCount is how many times this endpoint was seen across denial flush cycles. + HitCount int32 + // FirstSeen is the first time this endpoint was proposed. + FirstSeen time.Time + // LastSeen is the most recent time this endpoint was re-proposed. + LastSeen time.Time + // Binary is the binary path that triggered the denial. + Binary string + // ValidationResult is the prover output from gateway-side static checks. + ValidationResult string + // RejectionReason is the operator-supplied text accompanying a rejection. + RejectionReason string +} + +// DraftPolicy contains the full draft policy state returned by GetDraft. +type DraftPolicy struct { + // Chunks contains the draft policy chunks. + Chunks []PolicyChunk + // RollingSummary is an LLM-generated summary of all analysis. + RollingSummary string + // DraftVersion is the current draft version number. + DraftVersion uint64 + // LastAnalyzedAt is when the last analysis completed. + LastAnalyzedAt time.Time +} + +// SandboxPolicy is the top-level security policy configuration for a sandbox. +// It contains filesystem access rules, Landlock LSM configuration, process +// identity rules, and named network access policies. +type SandboxPolicy struct { + // Version is the policy version number. The server may override this on write. + Version uint32 + // Filesystem controls which directories the sandbox can access. + // Nil means no filesystem policy is specified. + Filesystem *FilesystemPolicy + // Landlock configures the Linux Landlock LSM. + // Nil means no landlock policy is specified. + Landlock *LandlockPolicy + // Process controls the user and group identity for sandboxed processes. + // Nil means no process policy is specified. + Process *ProcessPolicy + // NetworkPolicies contains named network access rules. + // Nil means no network policies are specified; an empty map is distinct from nil. + NetworkPolicies map[string]NetworkPolicyRule +} + +// FilesystemPolicy controls which directories the sandbox can access +// in read-only or read-write mode. +type FilesystemPolicy struct { + // IncludeWorkdir auto-includes the working directory as read-write. + IncludeWorkdir bool + // ReadOnly is the list of read-only directory paths. + // Nil means no read-only directories; an empty slice is distinct from nil. + ReadOnly []string + // ReadWrite is the list of read-write directory paths. + // Nil means no read-write directories; an empty slice is distinct from nil. + ReadWrite []string +} + +// LandlockPolicy configures the Linux Landlock LSM for filesystem restriction enforcement. +type LandlockPolicy struct { + // Compatibility is the compatibility mode (e.g., "best_effort", "hard_requirement"). + Compatibility string +} + +// ProcessPolicy controls the user and group identity under which sandboxed processes execute. +type ProcessPolicy struct { + // RunAsUser is the user name for sandboxed processes. + RunAsUser string + // RunAsGroup is the group name for sandboxed processes. + RunAsGroup string +} + +// SandboxPolicyRevision represents a versioned policy revision for a sandbox. +type SandboxPolicyRevision struct { + // Version is the policy version (monotonically increasing per sandbox). + Version uint32 + // PolicyHash is the SHA-256 hash of the serialized policy payload. + PolicyHash string + // Status is the load status of this revision. + Status PolicyLoadStatus + // LoadError is the error message if status is Failed. + LoadError string + // CreatedAt is when this revision was created. + CreatedAt time.Time + // LoadedAt is when this revision was loaded by the sandbox. + LoadedAt time.Time + // Policy is the typed security policy for this revision. Nil when not requested or absent. + Policy *SandboxPolicy +} + +// PolicyStatusResult contains the status of a sandbox's policy. +type PolicyStatusResult struct { + // Revision is the queried policy revision. + Revision SandboxPolicyRevision + // ActiveVersion is the currently active (loaded) policy version. + ActiveVersion uint32 +} + +// ApproveResult contains the result of approving a single draft chunk. +type ApproveResult struct { + // PolicyVersion is the new policy version after merge. + PolicyVersion uint32 + // PolicyHash is the SHA-256 hash of the new policy. + PolicyHash string +} + +// ApproveAllResult contains the result of approving all draft chunks. +type ApproveAllResult struct { + // PolicyVersion is the new policy version after merge. + PolicyVersion uint32 + // PolicyHash is the SHA-256 hash of the new policy. + PolicyHash string + // ChunksApproved is the number of chunks approved. + ChunksApproved uint32 + // ChunksSkipped is the number of chunks skipped (security-flagged). + ChunksSkipped uint32 +} + +// UndoResult contains the result of undoing a draft chunk approval. +type UndoResult struct { + // PolicyVersion is the new policy version after removal. + PolicyVersion uint32 + // PolicyHash is the SHA-256 hash of the updated policy. + PolicyHash string +} + +// ClearResult contains the result of clearing all draft chunks. +type ClearResult struct { + // ChunksCleared is the number of chunks cleared. + ChunksCleared uint32 +} + +// DraftHistoryEntry represents a single event in the draft policy history. +type DraftHistoryEntry struct { + // Timestamp is when the event occurred. + Timestamp time.Time + // EventType is the event type (e.g., "approved", "rejected", "cleared"). + EventType string + // Description is a human-readable description. + Description string + // ChunkID is the associated chunk ID (if applicable). + ChunkID string +} + +// getDraftConfig holds configuration for GetDraft calls. +type getDraftConfig struct { + statusFilter string +} + +// GetDraftOption configures a GetDraft call. +type GetDraftOption func(*getDraftConfig) + +// WithStatusFilter filters draft chunks by approval status. +func WithStatusFilter(status string) GetDraftOption { + return func(c *getDraftConfig) { + c.statusFilter = status + } +} + +// ApplyGetDraftOptions applies options and returns the config. +func ApplyGetDraftOptions(opts []GetDraftOption) getDraftConfig { //nolint:revive // unexported return is intentional; consumed only by v1 package + var cfg getDraftConfig + for _, opt := range opts { + opt(&cfg) + } + return cfg +} + +// StatusFilter returns the configured status filter. +func (c *getDraftConfig) StatusFilter() string { + return c.statusFilter +} + +// approveAllConfig holds configuration for ApproveAllDraftChunks calls. +type approveAllConfig struct { + includeSecurityFlagged bool +} + +// ApproveAllOption configures an ApproveAllDraftChunks call. +type ApproveAllOption func(*approveAllConfig) + +// WithIncludeSecurityFlagged includes security-flagged chunks in bulk approval. +func WithIncludeSecurityFlagged() ApproveAllOption { + return func(c *approveAllConfig) { + c.includeSecurityFlagged = true + } +} + +// ApplyApproveAllOptions applies options and returns the config. +func ApplyApproveAllOptions(opts []ApproveAllOption) approveAllConfig { //nolint:revive // unexported return is intentional; consumed only by v1 package + var cfg approveAllConfig + for _, opt := range opts { + opt(&cfg) + } + return cfg +} + +// IncludeSecurityFlagged returns whether security-flagged chunks are included. +func (c *approveAllConfig) IncludeSecurityFlagged() bool { + return c.includeSecurityFlagged +} + +// getStatusConfig holds configuration for GetStatus calls. +type getStatusConfig struct { + version uint32 +} + +// GetStatusOption configures a GetStatus call. +type GetStatusOption func(*getStatusConfig) + +// WithVersion queries a specific policy version instead of the latest. +func WithVersion(version uint32) GetStatusOption { + return func(c *getStatusConfig) { + c.version = version + } +} + +// ApplyGetStatusOptions applies options and returns the config. +func ApplyGetStatusOptions(opts []GetStatusOption) getStatusConfig { //nolint:revive // unexported return is intentional; consumed only by v1 package + var cfg getStatusConfig + for _, opt := range opts { + opt(&cfg) + } + return cfg +} + +// Version returns the configured version (0 means latest). +func (c *getStatusConfig) Version() uint32 { + return c.version +} + +// listPolicyConfig holds configuration for List calls. +type listPolicyConfig struct { + limit uint32 + offset uint32 +} + +// ListPolicyOption configures a List call. +type ListPolicyOption func(*listPolicyConfig) + +// WithLimit sets the maximum number of revisions to return. +func WithLimit(limit uint32) ListPolicyOption { + return func(c *listPolicyConfig) { + c.limit = limit + } +} + +// WithOffset sets the pagination offset. +func WithOffset(offset uint32) ListPolicyOption { + return func(c *listPolicyConfig) { + c.offset = offset + } +} + +// ApplyListPolicyOptions applies options and returns the config. +func ApplyListPolicyOptions(opts []ListPolicyOption) listPolicyConfig { //nolint:revive // unexported return is intentional; consumed only by v1 package + var cfg listPolicyConfig + for _, opt := range opts { + opt(&cfg) + } + return cfg +} + +// Limit returns the configured limit (0 means server default). +func (c *listPolicyConfig) Limit() uint32 { + return c.limit +} + +// Offset returns the configured offset. +func (c *listPolicyConfig) Offset() uint32 { + return c.offset +} diff --git a/sdk/go/openshell/v1/types/profile.go b/sdk/go/openshell/v1/types/profile.go new file mode 100644 index 000000000..0f987335a --- /dev/null +++ b/sdk/go/openshell/v1/types/profile.go @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +// ProfileCategory classifies a provider profile. +type ProfileCategory string + +// ProfileCategory values. +const ( + ProfileCategoryOther ProfileCategory = "Other" + ProfileCategoryInference ProfileCategory = "Inference" + ProfileCategoryAgent ProfileCategory = "Agent" + ProfileCategorySourceControl ProfileCategory = "SourceControl" + ProfileCategoryMessaging ProfileCategory = "Messaging" + ProfileCategoryData ProfileCategory = "Data" + ProfileCategoryKnowledge ProfileCategory = "Knowledge" +) + +// ProviderProfile defines a provider type template with credentials schema, +// endpoints, binaries, and discovery configuration. +type ProviderProfile struct { + ID string + DisplayName string + Description string + Category ProfileCategory + Credentials []ProfileCredential + Endpoints []NetworkEndpoint + Binaries []NetworkBinary + InferenceCapable bool + Discovery ProfileDiscovery + ResourceVersion uint64 +} + +// ProfileCredential defines a single credential required by a provider profile. +type ProfileCredential struct { + Name string + Description string + Required bool + Secret bool +} + +// NetworkEndpoint describes a network endpoint provided by a profile. +type NetworkEndpoint struct { + Host string + Port uint32 + Protocol string +} + +// NetworkBinary describes a binary artifact provided by a profile. +type NetworkBinary struct { + Path string +} + +// ProfileDiscovery holds local discovery configuration for a profile. +type ProfileDiscovery struct { + Credentials []string +} + +// ProfileImportItem is an item submitted for profile import or lint validation. +type ProfileImportItem struct { + Profile ProviderProfile + Source string +} + +// ProfileDiagnostic is a validation finding from Import, Update, or Lint. +type ProfileDiagnostic struct { + Source string + ProfileID string + Field string + Message string + Severity string +} + +// ImportResult holds the result of a profile import operation. +type ImportResult struct { + Diagnostics []ProfileDiagnostic + Profiles []ProviderProfile + Imported bool +} + +// UpdateResult holds the result of a profile update operation. +type UpdateResult struct { + Diagnostics []ProfileDiagnostic + Profile *ProviderProfile + Updated bool +} + +// LintResult holds the result of a profile lint operation. +type LintResult struct { + Diagnostics []ProfileDiagnostic + Valid bool +} diff --git a/sdk/go/openshell/v1/types/provider.go b/sdk/go/openshell/v1/types/provider.go new file mode 100644 index 000000000..7b8e4c2ef --- /dev/null +++ b/sdk/go/openshell/v1/types/provider.go @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import "time" + +// Provider represents an AI provider registration. +type Provider struct { + ID string + Name string + Type string + CreatedAt time.Time + Labels map[string]string + Annotations map[string]string + ResourceVersion uint64 + Workspace string + DeletionTimestamp *time.Time + Spec ProviderSpec +} + +// ProviderSpec holds provider-specific configuration and credentials. +type ProviderSpec struct { + Credentials map[string]string + Config map[string]string + CredentialExpiresAt map[string]time.Time + ProfileWorkspace string + CredentialHandles map[string]CredentialHandle +} + +// CredentialHandle is an opaque handle for a provider credential stored by +// gateway credential storage. Handles are created by OpenShell and are not +// accepted as user-authored input. +type CredentialHandle struct { + Driver string + Handle string + Metadata map[string]string +} diff --git a/sdk/go/openshell/v1/types/refresh.go b/sdk/go/openshell/v1/types/refresh.go new file mode 100644 index 000000000..b67ba3584 --- /dev/null +++ b/sdk/go/openshell/v1/types/refresh.go @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import "time" + +// RefreshStrategy describes how credentials are refreshed. +type RefreshStrategy string + +// RefreshStrategy values. +const ( + RefreshStrategyStatic RefreshStrategy = "Static" + RefreshStrategyExternal RefreshStrategy = "External" + RefreshStrategyOAuth2RefreshToken RefreshStrategy = "OAuth2RefreshToken" + RefreshStrategyOAuth2ClientCredentials RefreshStrategy = "OAuth2ClientCredentials" + RefreshStrategyGoogleServiceAccountJWT RefreshStrategy = "GoogleServiceAccountJWT" + RefreshStrategyAWSStsAssumeRole RefreshStrategy = "AWSStsAssumeRole" +) + +// RefreshStatus reports the current state of credential refresh for a specific +// provider credential. +type RefreshStatus struct { + ProviderName string + ProviderID string + CredentialKey string + Strategy RefreshStrategy + Status string + ExpiresAt time.Time + NextRefreshAt time.Time + LastRefreshAt time.Time + LastError string +} + +// RefreshConfig holds configuration parameters for gateway-owned credential +// refresh on a provider credential. +type RefreshConfig struct { + Provider string + CredentialKey string + Strategy RefreshStrategy + Material map[string]string + SecretMaterialKeys []string + ExpiresAt *time.Time +} diff --git a/sdk/go/openshell/v1/types/sandbox.go b/sdk/go/openshell/v1/types/sandbox.go new file mode 100644 index 000000000..97bf723eb --- /dev/null +++ b/sdk/go/openshell/v1/types/sandbox.go @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import "time" + +// Sandbox represents a sandbox instance. +type Sandbox struct { + ID string + Name string + CreatedAt time.Time + Labels map[string]string + Annotations map[string]string + ResourceVersion uint64 + Workspace string + DeletionTimestamp *time.Time + Spec SandboxSpec + Status SandboxStatus +} + +// SandboxSpec holds the desired state of a sandbox. +type SandboxSpec struct { + LogLevel string + Environment map[string]string + Template *SandboxTemplate + Providers []string + GPUCount *uint32 + // Policy is the security policy for the sandbox. Nil means no policy specified. + Policy *SandboxPolicy +} + +// SandboxTemplate defines the container template for a sandbox. +type SandboxTemplate struct { + Image string + RuntimeClassName string + AgentSocket string + Labels map[string]string + Annotations map[string]string + Environment map[string]string + Resources map[string]any + UserNamespaces *bool + DriverConfig map[string]any +} + +// SandboxStatus holds the observed state of a sandbox. +type SandboxStatus struct { + SandboxName string + AgentPod string + AgentFd string + SandboxFd string + Phase SandboxPhase + Conditions []SandboxCondition + CurrentPolicyVersion uint32 +} + +// SandboxCondition describes an observed condition of a sandbox. +type SandboxCondition struct { + Type string + Status string + Reason string + Message string + LastTransitionTime string +} + +// AttachProviderResult holds the result of attaching a provider to a sandbox. +type AttachProviderResult struct { + Sandbox *Sandbox + Attached bool +} + +// DetachProviderResult holds the result of detaching a provider from a sandbox. +type DetachProviderResult struct { + Sandbox *Sandbox + Detached bool +} diff --git a/sdk/go/openshell/v1/types/service.go b/sdk/go/openshell/v1/types/service.go new file mode 100644 index 000000000..c25cb9b63 --- /dev/null +++ b/sdk/go/openshell/v1/types/service.go @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +// ServiceEndpoint represents an exposed HTTP service on a sandbox. +type ServiceEndpoint struct { + ID string + SandboxID string + SandboxName string + ServiceName string + TargetPort uint32 + Domain bool + URL string +} diff --git a/sdk/go/openshell/v1/types/setting.go b/sdk/go/openshell/v1/types/setting.go new file mode 100644 index 000000000..005ff36c0 --- /dev/null +++ b/sdk/go/openshell/v1/types/setting.go @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +// SettingValueType identifies which typed field of a SettingValue is active. +type SettingValueType string + +// SettingValueType constants. +const ( + SettingValueString SettingValueType = "string" + SettingValueBool SettingValueType = "bool" + SettingValueInt SettingValueType = "int" + SettingValueBytes SettingValueType = "bytes" +) + +// SettingValue is a typed setting value supporting string, bool, int64, and bytes variants. +// The Type field indicates which value field is populated. +type SettingValue struct { + Type SettingValueType + StringVal string + BoolVal bool + IntVal int64 + BytesVal []byte +} + +// SettingScope indicates whether a setting is controlled at sandbox or global level. +type SettingScope string + +// SettingScope constants. +const ( + SettingScopeUnspecified SettingScope = "" + SettingScopeSandbox SettingScope = "sandbox" + SettingScopeGlobal SettingScope = "global" +) + +// PolicySource indicates the source of the policy payload in a SandboxConfig response. +type PolicySource string + +// PolicySource constants. +const ( + PolicySourceUnspecified PolicySource = "" + PolicySourceSandbox PolicySource = "sandbox" + PolicySourceGlobal PolicySource = "global" +) + +// EffectiveSetting is a setting value paired with the scope it was resolved from. +type EffectiveSetting struct { + Value SettingValue + Scope SettingScope +} + +// SandboxConfig represents the full configuration state of a sandbox, +// including policy, effective settings, and revision metadata. +type SandboxConfig struct { + // Policy is the typed security policy for this sandbox. Nil means no policy in the response. + Policy *SandboxPolicy + // PolicyVersion is monotonically increasing per sandbox. + PolicyVersion uint32 + // PolicyHash is the SHA-256 of the serialized policy payload. + PolicyHash string + // Settings is the effective settings resolved for this sandbox. + Settings map[string]EffectiveSetting + // ConfigRevision is the fingerprint for effective config (policy + settings). + ConfigRevision uint64 + // PolicySource indicates where the policy came from (sandbox or global). + PolicySource PolicySource + // GlobalPolicyVersion is the global policy version (0 if not applicable). + GlobalPolicyVersion uint32 + // ProviderEnvRevision is the fingerprint for provider credential inputs. + ProviderEnvRevision uint64 +} + +// GatewayConfig represents gateway-global settings. +type GatewayConfig struct { + // Settings is the global settings map. + Settings map[string]SettingValue + // SettingsRevision is a monotonically increasing revision for gateway-global settings. + SettingsRevision uint64 +} + +// ConfigUpdate represents a configuration mutation request. +// For sandbox-scoped updates, set Name to the sandbox name. +// For global-scoped updates, set Global to true. +type ConfigUpdate struct { + // Name is the sandbox name (required for sandbox-scoped updates). + Name string + // Policy is the typed security policy for a full policy replacement. Nil means no policy change. + Policy *SandboxPolicy + // SettingKey is a single setting key to mutate. + SettingKey string + // SettingValue is the setting value for upsert. Nil means no value change. + SettingValue *SettingValue + // DeleteSetting deletes the setting key when true. + DeleteSetting bool + // Global applies the update at gateway-global scope when true. + Global bool + // MergeOperations is a list of typed policy merge operations. + MergeOperations []PolicyMergeOperation + // ExpectedResourceVersion is for optimistic concurrency (0 = skip check). + ExpectedResourceVersion uint64 +} + +// ConfigUpdateResult holds the result of a configuration update operation. +// Named ConfigUpdateResult to avoid collision with profile.UpdateResult. +type ConfigUpdateResult struct { + // Version is the assigned policy version. + Version uint32 + // PolicyHash is the SHA-256 of the serialized policy. + PolicyHash string + // SettingsRevision is the settings revision for the modified scope. + SettingsRevision uint64 + // Deleted is true when a setting delete removed an existing key. + Deleted bool +} diff --git a/sdk/go/openshell/v1/types/ssh.go b/sdk/go/openshell/v1/types/ssh.go new file mode 100644 index 000000000..ec5e58e51 --- /dev/null +++ b/sdk/go/openshell/v1/types/ssh.go @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import "fmt" + +// SSHSession represents an SSH session created for a sandbox. +// The Token field is sensitive and MUST NOT be logged or included in error messages. +// The String() method redacts the token to prevent accidental exposure via fmt or logging. +type SSHSession struct { + // SandboxID is the sandbox this session connects to. + SandboxID string + // Token is the session token for gateway tunnel authentication. + // This is a sensitive credential — treat it like an API key. + Token string + // GatewayHost is the host for SSH proxy connection. + GatewayHost string + // GatewayPort is the gateway port (1-65535). + GatewayPort uint32 + // GatewayScheme is the gateway protocol scheme ("http" or "https"). + GatewayScheme string + // HostKeyFingerprint is the optional host key fingerprint. + HostKeyFingerprint string + // ExpiresAtMs is the session expiry in milliseconds since epoch. + // Zero means no expiry. + ExpiresAtMs int64 +} + +// String returns a human-readable representation with the Token redacted. +func (s SSHSession) String() string { + return fmt.Sprintf("SSHSession{SandboxID:%s, GatewayHost:%s, GatewayPort:%d, GatewayScheme:%s, Token:[REDACTED]}", + s.SandboxID, s.GatewayHost, s.GatewayPort, s.GatewayScheme) +} diff --git a/sdk/go/openshell/v1/types/types.go b/sdk/go/openshell/v1/types/types.go new file mode 100644 index 000000000..01da4ba9c --- /dev/null +++ b/sdk/go/openshell/v1/types/types.go @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import "time" + +// SandboxPhase represents the lifecycle phase of a sandbox. +type SandboxPhase string + +// SandboxPhase values for sandbox lifecycle. +const ( + SandboxProvisioning SandboxPhase = "Provisioning" + SandboxReady SandboxPhase = "Ready" + SandboxError SandboxPhase = "Error" + SandboxDeleting SandboxPhase = "Deleting" + SandboxUnknown SandboxPhase = "Unknown" +) + +// EventType classifies watch events. +type EventType string + +// EventType values for watch events. +const ( + EventAdded EventType = "ADDED" + EventModified EventType = "MODIFIED" + EventDeleted EventType = "DELETED" + EventError EventType = "ERROR" +) + +// StreamType identifies which output stream a chunk belongs to. +type StreamType string + +// StreamType values for exec output. +const ( + StreamStdout StreamType = "stdout" + StreamStderr StreamType = "stderr" +) + +// TLSConfig holds TLS connection settings. +type TLSConfig struct { + CertFile string + KeyFile string + CAFile string + // Insecure skips TLS certificate verification. Use http:// for plaintext. + Insecure bool +} + +// RetryPolicy configures automatic retry behavior for failed RPCs. +type RetryPolicy struct { + MaxRetries int + InitialWait time.Duration + MaxWait time.Duration +} diff --git a/sdk/go/openshell/v1/types/watch.go b/sdk/go/openshell/v1/types/watch.go new file mode 100644 index 000000000..0a8c9d498 --- /dev/null +++ b/sdk/go/openshell/v1/types/watch.go @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package types + +// Event represents a watch event carrying a resource that changed. +type Event[T any] struct { + Type EventType + Object T + Err error +} + +// WatchInterface delivers a stream of typed events. Modeled after +// k8s.io/apimachinery/pkg/watch.Interface. +type WatchInterface[T any] interface { + ResultChan() <-chan Event[T] + Stop() +} diff --git a/sdk/go/openshell/v1/types_reexport.go b/sdk/go/openshell/v1/types_reexport.go new file mode 100644 index 000000000..b92aa80bc --- /dev/null +++ b/sdk/go/openshell/v1/types_reexport.go @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// --- Network Policy types --- + +// NetworkPolicyRule defines a named network policy rule containing endpoints and binaries. +type NetworkPolicyRule = types.NetworkPolicyRule + +// PolicyNetworkEndpoint describes a full network endpoint in a sandbox network policy rule. +type PolicyNetworkEndpoint = types.PolicyNetworkEndpoint + +// PolicyNetworkBinary identifies a binary subject to network policy enforcement. +type PolicyNetworkBinary = types.PolicyNetworkBinary + +// L7Rule wraps an L7 allow rule. +type L7Rule = types.L7Rule + +// L7Allow specifies layer-7 allow criteria for HTTP/GraphQL traffic. +type L7Allow = types.L7Allow + +// L7DenyRule specifies layer-7 deny criteria for HTTP/GraphQL traffic. +type L7DenyRule = types.L7DenyRule + +// L7QueryMatcher matches query parameters by glob pattern or exact values. +type L7QueryMatcher = types.L7QueryMatcher + +// GraphqlOperation describes a GraphQL operation for persisted-query validation. +type GraphqlOperation = types.GraphqlOperation + +// --- MergeOperation types --- + +// PolicyMergeOperation represents a single atomic policy mutation. +type PolicyMergeOperation = types.PolicyMergeOperation + +// AddNetworkRule adds a named network policy rule with a full rule definition. +type AddNetworkRule = types.AddNetworkRule + +// RemoveNetworkEndpoint removes a specific endpoint from a named rule. +type RemoveNetworkEndpoint = types.RemoveNetworkEndpoint + +// RemoveNetworkRule removes an entire named rule from the policy. +type RemoveNetworkRule = types.RemoveNetworkRule + +// AddDenyRules appends layer-7 deny rules to a specific endpoint. +type AddDenyRules = types.AddDenyRules + +// AddAllowRules appends layer-7 allow rules to a specific endpoint. +type AddAllowRules = types.AddAllowRules + +// RemoveNetworkBinary removes a binary from a named rule. +type RemoveNetworkBinary = types.RemoveNetworkBinary diff --git a/sdk/go/openshell/v1/watch.go b/sdk/go/openshell/v1/watch.go new file mode 100644 index 000000000..696d8a9bd --- /dev/null +++ b/sdk/go/openshell/v1/watch.go @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "context" + "sync" + + "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types" +) + +// Event represents a watch event carrying a resource that changed. +type Event[T any] = types.Event[T] + +// WatchInterface delivers a stream of typed events. Modeled after +// k8s.io/apimachinery/pkg/watch.Interface. +type WatchInterface[T any] = types.WatchInterface[T] + +type watcher[T any] struct { + result chan Event[T] + done chan struct{} + cancel context.CancelFunc + stopOnce sync.Once +} + +func newWatcher[T any](ch chan Event[T], cancel context.CancelFunc) *watcher[T] { + return &watcher[T]{ + result: ch, + done: make(chan struct{}), + cancel: cancel, + } +} + +func (w *watcher[T]) ResultChan() <-chan Event[T] { + return w.result +} + +func (w *watcher[T]) Stop() { + w.stopOnce.Do(func() { + close(w.done) + if w.cancel != nil { + w.cancel() + } + }) +} diff --git a/sdk/go/openshell/v1/watch_test.go b/sdk/go/openshell/v1/watch_test.go new file mode 100644 index 000000000..1d6d5dc07 --- /dev/null +++ b/sdk/go/openshell/v1/watch_test.go @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1 + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// newTestWatcher creates a watcher with a simulated producer goroutine that +// forwards events from src to the watcher's channel and closes it when the +// producer finishes or Stop is called. +func newTestWatcher(src <-chan Event[string]) *watcher[string] { + ch := make(chan Event[string], 10) + w := newWatcher(ch, nil) + go func() { + defer close(ch) + for { + select { + case ev, ok := <-src: + if !ok { + return + } + select { + case ch <- ev: + case <-w.done: + return + } + case <-w.done: + return + } + } + }() + return w +} + +// --- T038: WatchInterface event delivery, Stop, and error handling --- + +func TestWatcher_DeliversEvents(t *testing.T) { + src := make(chan Event[string], 10) + w := newTestWatcher(src) + + src <- Event[string]{Type: EventAdded, Object: "sandbox-1"} + src <- Event[string]{Type: EventModified, Object: "sandbox-1"} + + resultCh := w.ResultChan() + + ev1 := <-resultCh + assert.Equal(t, EventAdded, ev1.Type) + assert.Equal(t, "sandbox-1", ev1.Object) + + ev2 := <-resultCh + assert.Equal(t, EventModified, ev2.Type) + assert.Equal(t, "sandbox-1", ev2.Object) +} + +func TestWatcher_StopClosesChannel(t *testing.T) { + src := make(chan Event[string], 10) + w := newTestWatcher(src) + + w.Stop() + + select { + case _, ok := <-w.ResultChan(): + assert.False(t, ok, "channel should be closed after Stop") + case <-time.After(time.Second): + t.Fatal("timed out waiting for channel close") + } +} + +func TestWatcher_StopIsIdempotent(_ *testing.T) { + src := make(chan Event[string], 10) + w := newTestWatcher(src) + + w.Stop() + w.Stop() // must not panic +} + +func TestWatcher_ErrorEvent(t *testing.T) { + src := make(chan Event[string], 10) + w := newTestWatcher(src) + + src <- Event[string]{Type: EventError, Object: "error details"} + + ev := <-w.ResultChan() + assert.Equal(t, EventError, ev.Type) + assert.Equal(t, "error details", ev.Object) +} + +func TestWatcher_ChannelClosesWhenSourceEnds(t *testing.T) { + src := make(chan Event[string], 10) + w := newTestWatcher(src) + + src <- Event[string]{Type: EventAdded, Object: "sb-1"} + close(src) + + ev := <-w.ResultChan() + require.Equal(t, "sb-1", ev.Object) + + select { + case _, ok := <-w.ResultChan(): + assert.False(t, ok, "channel should close when source ends") + case <-time.After(time.Second): + t.Fatal("timed out waiting for channel to close") + } +} + +func TestWatcher_DrainAfterStop(t *testing.T) { + src := make(chan Event[string], 10) + w := newTestWatcher(src) + + src <- Event[string]{Type: EventAdded, Object: "sb-1"} + + ev := <-w.ResultChan() + require.Equal(t, "sb-1", ev.Object) + + w.Stop() + + timeout := time.After(time.Second) + for { + select { + case _, ok := <-w.ResultChan(): + if !ok { + return // success: channel closed + } + case <-timeout: + t.Fatal("timed out waiting for channel to close after Stop") + } + } +} diff --git a/sdk/go/proto/datamodelv1/datamodel.pb.go b/sdk/go/proto/datamodelv1/datamodel.pb.go new file mode 100644 index 000000000..a672bf3d8 --- /dev/null +++ b/sdk/go/proto/datamodelv1/datamodel.pb.go @@ -0,0 +1,599 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: datamodel.proto + +package datamodelv1 + +import ( + _ "github.com/NVIDIA/OpenShell/sdk/go/proto/optionsv1" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Phase of a workspace's lifecycle. +type WorkspacePhase int32 + +const ( + WorkspacePhase_WORKSPACE_PHASE_UNSPECIFIED WorkspacePhase = 0 + WorkspacePhase_WORKSPACE_PHASE_ACTIVE WorkspacePhase = 1 + WorkspacePhase_WORKSPACE_PHASE_TERMINATING WorkspacePhase = 2 +) + +// Enum value maps for WorkspacePhase. +var ( + WorkspacePhase_name = map[int32]string{ + 0: "WORKSPACE_PHASE_UNSPECIFIED", + 1: "WORKSPACE_PHASE_ACTIVE", + 2: "WORKSPACE_PHASE_TERMINATING", + } + WorkspacePhase_value = map[string]int32{ + "WORKSPACE_PHASE_UNSPECIFIED": 0, + "WORKSPACE_PHASE_ACTIVE": 1, + "WORKSPACE_PHASE_TERMINATING": 2, + } +) + +func (x WorkspacePhase) Enum() *WorkspacePhase { + p := new(WorkspacePhase) + *p = x + return p +} + +func (x WorkspacePhase) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (WorkspacePhase) Descriptor() protoreflect.EnumDescriptor { + return file_datamodel_proto_enumTypes[0].Descriptor() +} + +func (WorkspacePhase) Type() protoreflect.EnumType { + return &file_datamodel_proto_enumTypes[0] +} + +func (x WorkspacePhase) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use WorkspacePhase.Descriptor instead. +func (WorkspacePhase) EnumDescriptor() ([]byte, []int) { + return file_datamodel_proto_rawDescGZIP(), []int{0} +} + +// Kubernetes-style metadata shared by all top-level OpenShell domain objects. +// +// This structure provides consistent metadata (identity, labels, annotations, +// timestamps, resource versioning) across Sandbox, Provider, SshSession, and +// other resources. +type ObjectMeta struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Stable object ID generated by the gateway. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Human-readable object name (unique per object type). + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // Milliseconds since Unix epoch when the object was created. + CreatedAtMs int64 `protobuf:"varint,3,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + // Key-value labels for filtering and organization. + // Labels must follow Kubernetes conventions: alphanumeric + `-._/`, max 63 chars per segment. + Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Optimistic concurrency control version. + // Incremented by the gateway on each update. Clients can use this for compare-and-swap operations. + ResourceVersion uint64 `protobuf:"varint,5,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // Opaque key-value metadata that is not used for selectors. + // Annotation keys use the same qualified-key shape as labels, but values may be longer. + Annotations map[string]string `protobuf:"bytes,6,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Workspace that owns this resource. Empty is normalized to "default" by the + // gateway. Immutable after creation. + Workspace string `protobuf:"bytes,7,opt,name=workspace,proto3" json:"workspace,omitempty"` + // Milliseconds since Unix epoch when graceful deletion was initiated. + // Zero means the object is not being deleted. Once set, this field is + // immutable — the only path forward is completing deletion. + DeletionTimestampMs int64 `protobuf:"varint,8,opt,name=deletion_timestamp_ms,json=deletionTimestampMs,proto3" json:"deletion_timestamp_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ObjectMeta) Reset() { + *x = ObjectMeta{} + mi := &file_datamodel_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ObjectMeta) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ObjectMeta) ProtoMessage() {} + +func (x *ObjectMeta) ProtoReflect() protoreflect.Message { + mi := &file_datamodel_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ObjectMeta.ProtoReflect.Descriptor instead. +func (*ObjectMeta) Descriptor() ([]byte, []int) { + return file_datamodel_proto_rawDescGZIP(), []int{0} +} + +func (x *ObjectMeta) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ObjectMeta) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ObjectMeta) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *ObjectMeta) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +func (x *ObjectMeta) GetResourceVersion() uint64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *ObjectMeta) GetAnnotations() map[string]string { + if x != nil { + return x.Annotations + } + return nil +} + +func (x *ObjectMeta) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +func (x *ObjectMeta) GetDeletionTimestampMs() int64 { + if x != nil { + return x.DeletionTimestampMs + } + return 0 +} + +// Status of a workspace. +type WorkspaceStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + Phase WorkspacePhase `protobuf:"varint,1,opt,name=phase,proto3,enum=openshell.datamodel.v1.WorkspacePhase" json:"phase,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WorkspaceStatus) Reset() { + *x = WorkspaceStatus{} + mi := &file_datamodel_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WorkspaceStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkspaceStatus) ProtoMessage() {} + +func (x *WorkspaceStatus) ProtoReflect() protoreflect.Message { + mi := &file_datamodel_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WorkspaceStatus.ProtoReflect.Descriptor instead. +func (*WorkspaceStatus) Descriptor() ([]byte, []int) { + return file_datamodel_proto_rawDescGZIP(), []int{1} +} + +func (x *WorkspaceStatus) GetPhase() WorkspacePhase { + if x != nil { + return x.Phase + } + return WorkspacePhase_WORKSPACE_PHASE_UNSPECIFIED +} + +// Workspace resource. A hard isolation boundary for sandboxes, providers, and +// other workspace-scoped resources. +type Workspace struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Kubernetes-style metadata (id, name, labels, timestamps, resource version). + // The workspace field in this ObjectMeta is unused (a workspace does not + // belong to another workspace). + Metadata *ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + // Current lifecycle status. + Status *WorkspaceStatus `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Workspace) Reset() { + *x = Workspace{} + mi := &file_datamodel_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Workspace) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Workspace) ProtoMessage() {} + +func (x *Workspace) ProtoReflect() protoreflect.Message { + mi := &file_datamodel_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Workspace.ProtoReflect.Descriptor instead. +func (*Workspace) Descriptor() ([]byte, []int) { + return file_datamodel_proto_rawDescGZIP(), []int{2} +} + +func (x *Workspace) GetMetadata() *ObjectMeta { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *Workspace) GetStatus() *WorkspaceStatus { + if x != nil { + return x.Status + } + return nil +} + +// Opaque handle for a provider credential stored by gateway credential storage. +// Handles are created by OpenShell and must not be authored by users. +type CredentialHandle struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Internal storage owner or credential driver that owns this handle. + Driver string `protobuf:"bytes,1,opt,name=driver,proto3" json:"driver,omitempty"` + // Owner-owned opaque handle string. + Handle string `protobuf:"bytes,2,opt,name=handle,proto3" json:"handle,omitempty"` + // Owner-owned non-secret metadata. + Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CredentialHandle) Reset() { + *x = CredentialHandle{} + mi := &file_datamodel_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CredentialHandle) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CredentialHandle) ProtoMessage() {} + +func (x *CredentialHandle) ProtoReflect() protoreflect.Message { + mi := &file_datamodel_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CredentialHandle.ProtoReflect.Descriptor instead. +func (*CredentialHandle) Descriptor() ([]byte, []int) { + return file_datamodel_proto_rawDescGZIP(), []int{3} +} + +func (x *CredentialHandle) GetDriver() string { + if x != nil { + return x.Driver + } + return "" +} + +func (x *CredentialHandle) GetHandle() string { + if x != nil { + return x.Handle + } + return "" +} + +func (x *CredentialHandle) GetMetadata() map[string]string { + if x != nil { + return x.Metadata + } + return nil +} + +// Provider model stored by OpenShell. +type Provider struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Kubernetes-style metadata (id, name, labels, timestamps, resource version). + Metadata *ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + // Canonical provider type slug (for example: "claude", "gitlab"). + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + // Secret values used for authentication. + Credentials map[string]string `protobuf:"bytes,3,rep,name=credentials,proto3" json:"credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Non-secret provider configuration. + Config map[string]string `protobuf:"bytes,4,rep,name=config,proto3" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Expiration timestamps for credential values, keyed by credential/env var + // name. A zero or missing value means the credential does not expire. + CredentialExpiresAtMs map[string]int64 `protobuf:"bytes,5,rep,name=credential_expires_at_ms,json=credentialExpiresAtMs,proto3" json:"credential_expires_at_ms,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + // Workspace where this provider's type profile is stored. + // Empty string = platform/global scope. Must be empty or match + // metadata.workspace; cross-workspace references are rejected. + ProfileWorkspace string `protobuf:"bytes,6,opt,name=profile_workspace,json=profileWorkspace,proto3" json:"profile_workspace,omitempty"` + // Opaque handles for secret values stored through gateway credential storage. + // This map is internal gateway state and is not accepted as user-authored input. + CredentialHandles map[string]*CredentialHandle `protobuf:"bytes,7,rep,name=credential_handles,json=credentialHandles,proto3" json:"credential_handles,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Provider) Reset() { + *x = Provider{} + mi := &file_datamodel_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Provider) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Provider) ProtoMessage() {} + +func (x *Provider) ProtoReflect() protoreflect.Message { + mi := &file_datamodel_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Provider.ProtoReflect.Descriptor instead. +func (*Provider) Descriptor() ([]byte, []int) { + return file_datamodel_proto_rawDescGZIP(), []int{4} +} + +func (x *Provider) GetMetadata() *ObjectMeta { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *Provider) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Provider) GetCredentials() map[string]string { + if x != nil { + return x.Credentials + } + return nil +} + +func (x *Provider) GetConfig() map[string]string { + if x != nil { + return x.Config + } + return nil +} + +func (x *Provider) GetCredentialExpiresAtMs() map[string]int64 { + if x != nil { + return x.CredentialExpiresAtMs + } + return nil +} + +func (x *Provider) GetProfileWorkspace() string { + if x != nil { + return x.ProfileWorkspace + } + return "" +} + +func (x *Provider) GetCredentialHandles() map[string]*CredentialHandle { + if x != nil { + return x.CredentialHandles + } + return nil +} + +var File_datamodel_proto protoreflect.FileDescriptor + +const file_datamodel_proto_rawDesc = "" + + "\n" + + "\x0fdatamodel.proto\x12\x16openshell.datamodel.v1\x1a\roptions.proto\"\xeb\x03\n" + + "\n" + + "ObjectMeta\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\"\n" + + "\rcreated_at_ms\x18\x03 \x01(\x03R\vcreatedAtMs\x12F\n" + + "\x06labels\x18\x04 \x03(\v2..openshell.datamodel.v1.ObjectMeta.LabelsEntryR\x06labels\x12)\n" + + "\x10resource_version\x18\x05 \x01(\x04R\x0fresourceVersion\x12U\n" + + "\vannotations\x18\x06 \x03(\v23.openshell.datamodel.v1.ObjectMeta.AnnotationsEntryR\vannotations\x12\x1c\n" + + "\tworkspace\x18\a \x01(\tR\tworkspace\x122\n" + + "\x15deletion_timestamp_ms\x18\b \x01(\x03R\x13deletionTimestampMs\x1a9\n" + + "\vLabelsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a>\n" + + "\x10AnnotationsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"O\n" + + "\x0fWorkspaceStatus\x12<\n" + + "\x05phase\x18\x01 \x01(\x0e2&.openshell.datamodel.v1.WorkspacePhaseR\x05phase\"\x8c\x01\n" + + "\tWorkspace\x12>\n" + + "\bmetadata\x18\x01 \x01(\v2\".openshell.datamodel.v1.ObjectMetaR\bmetadata\x12?\n" + + "\x06status\x18\x02 \x01(\v2'.openshell.datamodel.v1.WorkspaceStatusR\x06status\"\xd3\x01\n" + + "\x10CredentialHandle\x12\x16\n" + + "\x06driver\x18\x01 \x01(\tR\x06driver\x12\x16\n" + + "\x06handle\x18\x02 \x01(\tR\x06handle\x12R\n" + + "\bmetadata\x18\x03 \x03(\v26.openshell.datamodel.v1.CredentialHandle.MetadataEntryR\bmetadata\x1a;\n" + + "\rMetadataEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xbf\x06\n" + + "\bProvider\x12>\n" + + "\bmetadata\x18\x01 \x01(\v2\".openshell.datamodel.v1.ObjectMetaR\bmetadata\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\x12Y\n" + + "\vcredentials\x18\x03 \x03(\v21.openshell.datamodel.v1.Provider.CredentialsEntryB\x04\x88\xb5\x18\x01R\vcredentials\x12D\n" + + "\x06config\x18\x04 \x03(\v2,.openshell.datamodel.v1.Provider.ConfigEntryR\x06config\x12t\n" + + "\x18credential_expires_at_ms\x18\x05 \x03(\v2;.openshell.datamodel.v1.Provider.CredentialExpiresAtMsEntryR\x15credentialExpiresAtMs\x12+\n" + + "\x11profile_workspace\x18\x06 \x01(\tR\x10profileWorkspace\x12f\n" + + "\x12credential_handles\x18\a \x03(\v27.openshell.datamodel.v1.Provider.CredentialHandlesEntryR\x11credentialHandles\x1a>\n" + + "\x10CredentialsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a9\n" + + "\vConfigEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1aH\n" + + "\x1aCredentialExpiresAtMsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\x03R\x05value:\x028\x01\x1an\n" + + "\x16CredentialHandlesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12>\n" + + "\x05value\x18\x02 \x01(\v2(.openshell.datamodel.v1.CredentialHandleR\x05value:\x028\x01*n\n" + + "\x0eWorkspacePhase\x12\x1f\n" + + "\x1bWORKSPACE_PHASE_UNSPECIFIED\x10\x00\x12\x1a\n" + + "\x16WORKSPACE_PHASE_ACTIVE\x10\x01\x12\x1f\n" + + "\x1bWORKSPACE_PHASE_TERMINATING\x10\x02b\x06proto3" + +var ( + file_datamodel_proto_rawDescOnce sync.Once + file_datamodel_proto_rawDescData []byte +) + +func file_datamodel_proto_rawDescGZIP() []byte { + file_datamodel_proto_rawDescOnce.Do(func() { + file_datamodel_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_datamodel_proto_rawDesc), len(file_datamodel_proto_rawDesc))) + }) + return file_datamodel_proto_rawDescData +} + +var file_datamodel_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_datamodel_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_datamodel_proto_goTypes = []any{ + (WorkspacePhase)(0), // 0: openshell.datamodel.v1.WorkspacePhase + (*ObjectMeta)(nil), // 1: openshell.datamodel.v1.ObjectMeta + (*WorkspaceStatus)(nil), // 2: openshell.datamodel.v1.WorkspaceStatus + (*Workspace)(nil), // 3: openshell.datamodel.v1.Workspace + (*CredentialHandle)(nil), // 4: openshell.datamodel.v1.CredentialHandle + (*Provider)(nil), // 5: openshell.datamodel.v1.Provider + nil, // 6: openshell.datamodel.v1.ObjectMeta.LabelsEntry + nil, // 7: openshell.datamodel.v1.ObjectMeta.AnnotationsEntry + nil, // 8: openshell.datamodel.v1.CredentialHandle.MetadataEntry + nil, // 9: openshell.datamodel.v1.Provider.CredentialsEntry + nil, // 10: openshell.datamodel.v1.Provider.ConfigEntry + nil, // 11: openshell.datamodel.v1.Provider.CredentialExpiresAtMsEntry + nil, // 12: openshell.datamodel.v1.Provider.CredentialHandlesEntry +} +var file_datamodel_proto_depIdxs = []int32{ + 6, // 0: openshell.datamodel.v1.ObjectMeta.labels:type_name -> openshell.datamodel.v1.ObjectMeta.LabelsEntry + 7, // 1: openshell.datamodel.v1.ObjectMeta.annotations:type_name -> openshell.datamodel.v1.ObjectMeta.AnnotationsEntry + 0, // 2: openshell.datamodel.v1.WorkspaceStatus.phase:type_name -> openshell.datamodel.v1.WorkspacePhase + 1, // 3: openshell.datamodel.v1.Workspace.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 2, // 4: openshell.datamodel.v1.Workspace.status:type_name -> openshell.datamodel.v1.WorkspaceStatus + 8, // 5: openshell.datamodel.v1.CredentialHandle.metadata:type_name -> openshell.datamodel.v1.CredentialHandle.MetadataEntry + 1, // 6: openshell.datamodel.v1.Provider.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 9, // 7: openshell.datamodel.v1.Provider.credentials:type_name -> openshell.datamodel.v1.Provider.CredentialsEntry + 10, // 8: openshell.datamodel.v1.Provider.config:type_name -> openshell.datamodel.v1.Provider.ConfigEntry + 11, // 9: openshell.datamodel.v1.Provider.credential_expires_at_ms:type_name -> openshell.datamodel.v1.Provider.CredentialExpiresAtMsEntry + 12, // 10: openshell.datamodel.v1.Provider.credential_handles:type_name -> openshell.datamodel.v1.Provider.CredentialHandlesEntry + 4, // 11: openshell.datamodel.v1.Provider.CredentialHandlesEntry.value:type_name -> openshell.datamodel.v1.CredentialHandle + 12, // [12:12] is the sub-list for method output_type + 12, // [12:12] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name +} + +func init() { file_datamodel_proto_init() } +func file_datamodel_proto_init() { + if File_datamodel_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_datamodel_proto_rawDesc), len(file_datamodel_proto_rawDesc)), + NumEnums: 1, + NumMessages: 12, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_datamodel_proto_goTypes, + DependencyIndexes: file_datamodel_proto_depIdxs, + EnumInfos: file_datamodel_proto_enumTypes, + MessageInfos: file_datamodel_proto_msgTypes, + }.Build() + File_datamodel_proto = out.File + file_datamodel_proto_goTypes = nil + file_datamodel_proto_depIdxs = nil +} diff --git a/sdk/go/proto/openshellv1/openshell.pb.go b/sdk/go/proto/openshellv1/openshell.pb.go new file mode 100644 index 000000000..2696be0e0 --- /dev/null +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -0,0 +1,14464 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: openshell.proto + +package openshellv1 + +import ( + datamodelv1 "github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1" + _ "github.com/NVIDIA/OpenShell/sdk/go/proto/optionsv1" + sandboxv1 "github.com/NVIDIA/OpenShell/sdk/go/proto/sandboxv1" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// 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. +type SandboxPhase int32 + +const ( + SandboxPhase_SANDBOX_PHASE_UNSPECIFIED SandboxPhase = 0 + SandboxPhase_SANDBOX_PHASE_PROVISIONING SandboxPhase = 1 + SandboxPhase_SANDBOX_PHASE_READY SandboxPhase = 2 + SandboxPhase_SANDBOX_PHASE_ERROR SandboxPhase = 3 + SandboxPhase_SANDBOX_PHASE_DELETING SandboxPhase = 4 + SandboxPhase_SANDBOX_PHASE_UNKNOWN SandboxPhase = 5 +) + +// Enum value maps for SandboxPhase. +var ( + SandboxPhase_name = map[int32]string{ + 0: "SANDBOX_PHASE_UNSPECIFIED", + 1: "SANDBOX_PHASE_PROVISIONING", + 2: "SANDBOX_PHASE_READY", + 3: "SANDBOX_PHASE_ERROR", + 4: "SANDBOX_PHASE_DELETING", + 5: "SANDBOX_PHASE_UNKNOWN", + } + SandboxPhase_value = map[string]int32{ + "SANDBOX_PHASE_UNSPECIFIED": 0, + "SANDBOX_PHASE_PROVISIONING": 1, + "SANDBOX_PHASE_READY": 2, + "SANDBOX_PHASE_ERROR": 3, + "SANDBOX_PHASE_DELETING": 4, + "SANDBOX_PHASE_UNKNOWN": 5, + } +) + +func (x SandboxPhase) Enum() *SandboxPhase { + p := new(SandboxPhase) + *p = x + return p +} + +func (x SandboxPhase) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SandboxPhase) Descriptor() protoreflect.EnumDescriptor { + return file_openshell_proto_enumTypes[0].Descriptor() +} + +func (SandboxPhase) Type() protoreflect.EnumType { + return &file_openshell_proto_enumTypes[0] +} + +func (x SandboxPhase) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SandboxPhase.Descriptor instead. +func (SandboxPhase) EnumDescriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{0} +} + +type ProviderCredentialRefreshStrategy int32 + +const ( + ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED ProviderCredentialRefreshStrategy = 0 + ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_STATIC ProviderCredentialRefreshStrategy = 1 + ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_EXTERNAL ProviderCredentialRefreshStrategy = 2 + ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_OAUTH2_REFRESH_TOKEN ProviderCredentialRefreshStrategy = 3 + ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_OAUTH2_CLIENT_CREDENTIALS ProviderCredentialRefreshStrategy = 4 + ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_GOOGLE_SERVICE_ACCOUNT_JWT ProviderCredentialRefreshStrategy = 5 + ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_AWS_STS_ASSUME_ROLE ProviderCredentialRefreshStrategy = 6 +) + +// Enum value maps for ProviderCredentialRefreshStrategy. +var ( + ProviderCredentialRefreshStrategy_name = map[int32]string{ + 0: "PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED", + 1: "PROVIDER_CREDENTIAL_REFRESH_STRATEGY_STATIC", + 2: "PROVIDER_CREDENTIAL_REFRESH_STRATEGY_EXTERNAL", + 3: "PROVIDER_CREDENTIAL_REFRESH_STRATEGY_OAUTH2_REFRESH_TOKEN", + 4: "PROVIDER_CREDENTIAL_REFRESH_STRATEGY_OAUTH2_CLIENT_CREDENTIALS", + 5: "PROVIDER_CREDENTIAL_REFRESH_STRATEGY_GOOGLE_SERVICE_ACCOUNT_JWT", + 6: "PROVIDER_CREDENTIAL_REFRESH_STRATEGY_AWS_STS_ASSUME_ROLE", + } + ProviderCredentialRefreshStrategy_value = map[string]int32{ + "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, + } +) + +func (x ProviderCredentialRefreshStrategy) Enum() *ProviderCredentialRefreshStrategy { + p := new(ProviderCredentialRefreshStrategy) + *p = x + return p +} + +func (x ProviderCredentialRefreshStrategy) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ProviderCredentialRefreshStrategy) Descriptor() protoreflect.EnumDescriptor { + return file_openshell_proto_enumTypes[1].Descriptor() +} + +func (ProviderCredentialRefreshStrategy) Type() protoreflect.EnumType { + return &file_openshell_proto_enumTypes[1] +} + +func (x ProviderCredentialRefreshStrategy) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ProviderCredentialRefreshStrategy.Descriptor instead. +func (ProviderCredentialRefreshStrategy) EnumDescriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{1} +} + +// Stable provider profile categories used by clients for grouping and filtering. +type ProviderProfileCategory int32 + +const ( + ProviderProfileCategory_PROVIDER_PROFILE_CATEGORY_UNSPECIFIED ProviderProfileCategory = 0 + ProviderProfileCategory_PROVIDER_PROFILE_CATEGORY_OTHER ProviderProfileCategory = 1 + ProviderProfileCategory_PROVIDER_PROFILE_CATEGORY_INFERENCE ProviderProfileCategory = 2 + ProviderProfileCategory_PROVIDER_PROFILE_CATEGORY_AGENT ProviderProfileCategory = 3 + ProviderProfileCategory_PROVIDER_PROFILE_CATEGORY_SOURCE_CONTROL ProviderProfileCategory = 4 + ProviderProfileCategory_PROVIDER_PROFILE_CATEGORY_MESSAGING ProviderProfileCategory = 5 + ProviderProfileCategory_PROVIDER_PROFILE_CATEGORY_DATA ProviderProfileCategory = 6 + ProviderProfileCategory_PROVIDER_PROFILE_CATEGORY_KNOWLEDGE ProviderProfileCategory = 7 +) + +// Enum value maps for ProviderProfileCategory. +var ( + ProviderProfileCategory_name = map[int32]string{ + 0: "PROVIDER_PROFILE_CATEGORY_UNSPECIFIED", + 1: "PROVIDER_PROFILE_CATEGORY_OTHER", + 2: "PROVIDER_PROFILE_CATEGORY_INFERENCE", + 3: "PROVIDER_PROFILE_CATEGORY_AGENT", + 4: "PROVIDER_PROFILE_CATEGORY_SOURCE_CONTROL", + 5: "PROVIDER_PROFILE_CATEGORY_MESSAGING", + 6: "PROVIDER_PROFILE_CATEGORY_DATA", + 7: "PROVIDER_PROFILE_CATEGORY_KNOWLEDGE", + } + ProviderProfileCategory_value = map[string]int32{ + "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, + } +) + +func (x ProviderProfileCategory) Enum() *ProviderProfileCategory { + p := new(ProviderProfileCategory) + *p = x + return p +} + +func (x ProviderProfileCategory) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ProviderProfileCategory) Descriptor() protoreflect.EnumDescriptor { + return file_openshell_proto_enumTypes[2].Descriptor() +} + +func (ProviderProfileCategory) Type() protoreflect.EnumType { + return &file_openshell_proto_enumTypes[2] +} + +func (x ProviderProfileCategory) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ProviderProfileCategory.Descriptor instead. +func (ProviderProfileCategory) EnumDescriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{2} +} + +// Policy load status. +type PolicyStatus int32 + +const ( + PolicyStatus_POLICY_STATUS_UNSPECIFIED PolicyStatus = 0 + // Server received the update; sandbox has not yet loaded it. + PolicyStatus_POLICY_STATUS_PENDING PolicyStatus = 1 + // Sandbox successfully applied this policy version. + PolicyStatus_POLICY_STATUS_LOADED PolicyStatus = 2 + // Sandbox attempted to apply but failed; LKG policy remains active. + PolicyStatus_POLICY_STATUS_FAILED PolicyStatus = 3 + // A newer version was persisted before the sandbox loaded this one. + PolicyStatus_POLICY_STATUS_SUPERSEDED PolicyStatus = 4 +) + +// Enum value maps for PolicyStatus. +var ( + PolicyStatus_name = map[int32]string{ + 0: "POLICY_STATUS_UNSPECIFIED", + 1: "POLICY_STATUS_PENDING", + 2: "POLICY_STATUS_LOADED", + 3: "POLICY_STATUS_FAILED", + 4: "POLICY_STATUS_SUPERSEDED", + } + PolicyStatus_value = map[string]int32{ + "POLICY_STATUS_UNSPECIFIED": 0, + "POLICY_STATUS_PENDING": 1, + "POLICY_STATUS_LOADED": 2, + "POLICY_STATUS_FAILED": 3, + "POLICY_STATUS_SUPERSEDED": 4, + } +) + +func (x PolicyStatus) Enum() *PolicyStatus { + p := new(PolicyStatus) + *p = x + return p +} + +func (x PolicyStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PolicyStatus) Descriptor() protoreflect.EnumDescriptor { + return file_openshell_proto_enumTypes[3].Descriptor() +} + +func (PolicyStatus) Type() protoreflect.EnumType { + return &file_openshell_proto_enumTypes[3] +} + +func (x PolicyStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PolicyStatus.Descriptor instead. +func (PolicyStatus) EnumDescriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{3} +} + +// Service status enum. +type ServiceStatus int32 + +const ( + ServiceStatus_SERVICE_STATUS_UNSPECIFIED ServiceStatus = 0 + ServiceStatus_SERVICE_STATUS_HEALTHY ServiceStatus = 1 + ServiceStatus_SERVICE_STATUS_DEGRADED ServiceStatus = 2 + ServiceStatus_SERVICE_STATUS_UNHEALTHY ServiceStatus = 3 +) + +// Enum value maps for ServiceStatus. +var ( + ServiceStatus_name = map[int32]string{ + 0: "SERVICE_STATUS_UNSPECIFIED", + 1: "SERVICE_STATUS_HEALTHY", + 2: "SERVICE_STATUS_DEGRADED", + 3: "SERVICE_STATUS_UNHEALTHY", + } + ServiceStatus_value = map[string]int32{ + "SERVICE_STATUS_UNSPECIFIED": 0, + "SERVICE_STATUS_HEALTHY": 1, + "SERVICE_STATUS_DEGRADED": 2, + "SERVICE_STATUS_UNHEALTHY": 3, + } +) + +func (x ServiceStatus) Enum() *ServiceStatus { + p := new(ServiceStatus) + *p = x + return p +} + +func (x ServiceStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ServiceStatus) Descriptor() protoreflect.EnumDescriptor { + return file_openshell_proto_enumTypes[4].Descriptor() +} + +func (ServiceStatus) Type() protoreflect.EnumType { + return &file_openshell_proto_enumTypes[4] +} + +func (x ServiceStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ServiceStatus.Descriptor instead. +func (ServiceStatus) EnumDescriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{4} +} + +// Workspace-scoped role for members. +type WorkspaceRole int32 + +const ( + WorkspaceRole_WORKSPACE_ROLE_UNSPECIFIED WorkspaceRole = 0 + WorkspaceRole_WORKSPACE_ROLE_USER WorkspaceRole = 1 + WorkspaceRole_WORKSPACE_ROLE_ADMIN WorkspaceRole = 2 +) + +// Enum value maps for WorkspaceRole. +var ( + WorkspaceRole_name = map[int32]string{ + 0: "WORKSPACE_ROLE_UNSPECIFIED", + 1: "WORKSPACE_ROLE_USER", + 2: "WORKSPACE_ROLE_ADMIN", + } + WorkspaceRole_value = map[string]int32{ + "WORKSPACE_ROLE_UNSPECIFIED": 0, + "WORKSPACE_ROLE_USER": 1, + "WORKSPACE_ROLE_ADMIN": 2, + } +) + +func (x WorkspaceRole) Enum() *WorkspaceRole { + p := new(WorkspaceRole) + *p = x + return p +} + +func (x WorkspaceRole) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (WorkspaceRole) Descriptor() protoreflect.EnumDescriptor { + return file_openshell_proto_enumTypes[5].Descriptor() +} + +func (WorkspaceRole) Type() protoreflect.EnumType { + return &file_openshell_proto_enumTypes[5] +} + +func (x WorkspaceRole) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use WorkspaceRole.Descriptor instead. +func (WorkspaceRole) EnumDescriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{5} +} + +// 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). +type IssueSandboxTokenRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *IssueSandboxTokenRequest) Reset() { + *x = IssueSandboxTokenRequest{} + mi := &file_openshell_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IssueSandboxTokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IssueSandboxTokenRequest) ProtoMessage() {} + +func (x *IssueSandboxTokenRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IssueSandboxTokenRequest.ProtoReflect.Descriptor instead. +func (*IssueSandboxTokenRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{0} +} + +// IssueSandboxToken response. The supervisor caches the returned token in +// memory and presents it as `Authorization: Bearer` on every subsequent +// gateway RPC. +type IssueSandboxTokenResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Gateway-minted JWT bound to the calling sandbox's UUID. + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + // Absolute expiry of the issued token, milliseconds since the epoch. 0 means + // the token is non-expiring. + ExpiresAtMs int64 `protobuf:"varint,2,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *IssueSandboxTokenResponse) Reset() { + *x = IssueSandboxTokenResponse{} + mi := &file_openshell_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IssueSandboxTokenResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IssueSandboxTokenResponse) ProtoMessage() {} + +func (x *IssueSandboxTokenResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IssueSandboxTokenResponse.ProtoReflect.Descriptor instead. +func (*IssueSandboxTokenResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{1} +} + +func (x *IssueSandboxTokenResponse) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *IssueSandboxTokenResponse) GetExpiresAtMs() int64 { + if x != nil { + return x.ExpiresAtMs + } + return 0 +} + +// RefreshSandboxToken request. Empty body; the calling principal must +// already be a sandbox principal (i.e. the request carries a still-valid +// gateway-minted JWT in its Authorization header). +type RefreshSandboxTokenRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RefreshSandboxTokenRequest) Reset() { + *x = RefreshSandboxTokenRequest{} + mi := &file_openshell_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RefreshSandboxTokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RefreshSandboxTokenRequest) ProtoMessage() {} + +func (x *RefreshSandboxTokenRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RefreshSandboxTokenRequest.ProtoReflect.Descriptor instead. +func (*RefreshSandboxTokenRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{2} +} + +// RefreshSandboxToken response. The new token replaces the supervisor's +// in-memory bearer credential. +type RefreshSandboxTokenResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Fresh gateway-minted JWT bound to the same sandbox UUID. + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + // Absolute expiry of the new token, milliseconds since the epoch. 0 means + // the token is non-expiring. + ExpiresAtMs int64 `protobuf:"varint,2,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RefreshSandboxTokenResponse) Reset() { + *x = RefreshSandboxTokenResponse{} + mi := &file_openshell_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RefreshSandboxTokenResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RefreshSandboxTokenResponse) ProtoMessage() {} + +func (x *RefreshSandboxTokenResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RefreshSandboxTokenResponse.ProtoReflect.Descriptor instead. +func (*RefreshSandboxTokenResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{3} +} + +func (x *RefreshSandboxTokenResponse) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *RefreshSandboxTokenResponse) GetExpiresAtMs() int64 { + if x != nil { + return x.ExpiresAtMs + } + return 0 +} + +// Health check request. +type HealthRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HealthRequest) Reset() { + *x = HealthRequest{} + mi := &file_openshell_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HealthRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthRequest) ProtoMessage() {} + +func (x *HealthRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HealthRequest.ProtoReflect.Descriptor instead. +func (*HealthRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{4} +} + +// Health check response. +type HealthResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Service status. + Status ServiceStatus `protobuf:"varint,1,opt,name=status,proto3,enum=openshell.v1.ServiceStatus" json:"status,omitempty"` + // Service version. + Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HealthResponse) Reset() { + *x = HealthResponse{} + mi := &file_openshell_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HealthResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthResponse) ProtoMessage() {} + +func (x *HealthResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HealthResponse.ProtoReflect.Descriptor instead. +func (*HealthResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{5} +} + +func (x *HealthResponse) GetStatus() ServiceStatus { + if x != nil { + return x.Status + } + return ServiceStatus_SERVICE_STATUS_UNSPECIFIED +} + +func (x *HealthResponse) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +// Current-user request. The identity comes from the authenticated request. +type GetCurrentUserRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCurrentUserRequest) Reset() { + *x = GetCurrentUserRequest{} + mi := &file_openshell_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCurrentUserRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCurrentUserRequest) ProtoMessage() {} + +func (x *GetCurrentUserRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCurrentUserRequest.ProtoReflect.Descriptor instead. +func (*GetCurrentUserRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{6} +} + +// Authenticated user identity as validated by the gateway. +type GetCurrentUserResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Stable identity subject (for example, the OIDC `sub` claim). + Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"` + // Human-readable identity name when supplied by the authentication provider. + DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + // Roles granted to the authenticated identity. + Roles []string `protobuf:"bytes,3,rep,name=roles,proto3" json:"roles,omitempty"` + // OAuth2 scopes granted to the authenticated identity. + Scopes []string `protobuf:"bytes,4,rep,name=scopes,proto3" json:"scopes,omitempty"` + // Authentication provider that established the identity. + IdentityProvider string `protobuf:"bytes,5,opt,name=identity_provider,json=identityProvider,proto3" json:"identity_provider,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCurrentUserResponse) Reset() { + *x = GetCurrentUserResponse{} + mi := &file_openshell_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCurrentUserResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCurrentUserResponse) ProtoMessage() {} + +func (x *GetCurrentUserResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCurrentUserResponse.ProtoReflect.Descriptor instead. +func (*GetCurrentUserResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{7} +} + +func (x *GetCurrentUserResponse) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *GetCurrentUserResponse) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *GetCurrentUserResponse) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + +func (x *GetCurrentUserResponse) GetScopes() []string { + if x != nil { + return x.Scopes + } + return nil +} + +func (x *GetCurrentUserResponse) GetIdentityProvider() string { + if x != nil { + return x.IdentityProvider + } + return "" +} + +// Gateway info request. +type GetGatewayInfoRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetGatewayInfoRequest) Reset() { + *x = GetGatewayInfoRequest{} + mi := &file_openshell_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetGatewayInfoRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetGatewayInfoRequest) ProtoMessage() {} + +func (x *GetGatewayInfoRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetGatewayInfoRequest.ProtoReflect.Descriptor instead. +func (*GetGatewayInfoRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{8} +} + +// Gateway info response. +type GetGatewayInfoResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Service status. + Status ServiceStatus `protobuf:"varint,1,opt,name=status,proto3,enum=openshell.v1.ServiceStatus" json:"status,omitempty"` + // OpenShell gateway binary version. + GatewayVersion string `protobuf:"bytes,2,opt,name=gateway_version,json=gatewayVersion,proto3" json:"gateway_version,omitempty"` + // Compute driver runtimes initialized by this gateway. Current gateways + // return exactly one entry. + ComputeDrivers []*ComputeDriverInfo `protobuf:"bytes,3,rep,name=compute_drivers,json=computeDrivers,proto3" json:"compute_drivers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetGatewayInfoResponse) Reset() { + *x = GetGatewayInfoResponse{} + mi := &file_openshell_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetGatewayInfoResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetGatewayInfoResponse) ProtoMessage() {} + +func (x *GetGatewayInfoResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetGatewayInfoResponse.ProtoReflect.Descriptor instead. +func (*GetGatewayInfoResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{9} +} + +func (x *GetGatewayInfoResponse) GetStatus() ServiceStatus { + if x != nil { + return x.Status + } + return ServiceStatus_SERVICE_STATUS_UNSPECIFIED +} + +func (x *GetGatewayInfoResponse) GetGatewayVersion() string { + if x != nil { + return x.GatewayVersion + } + return "" +} + +func (x *GetGatewayInfoResponse) GetComputeDrivers() []*ComputeDriverInfo { + if x != nil { + return x.ComputeDrivers + } + return nil +} + +// Info for one initialized compute driver runtime. +type ComputeDriverInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Gateway-selected driver name used for routing and driver_config keys. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Capabilities reported by the driver during gateway runtime initialization. + Capabilities *ComputeDriverCapabilities `protobuf:"bytes,2,opt,name=capabilities,proto3" json:"capabilities,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ComputeDriverInfo) Reset() { + *x = ComputeDriverInfo{} + mi := &file_openshell_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ComputeDriverInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ComputeDriverInfo) ProtoMessage() {} + +func (x *ComputeDriverInfo) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ComputeDriverInfo.ProtoReflect.Descriptor instead. +func (*ComputeDriverInfo) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{10} +} + +func (x *ComputeDriverInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ComputeDriverInfo) GetCapabilities() *ComputeDriverCapabilities { + if x != nil { + return x.Capabilities + } + return nil +} + +// Public compute driver capability snapshot. +type ComputeDriverCapabilities struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Driver-reported human-readable name from the startup capability snapshot. + DriverName string `protobuf:"bytes,1,opt,name=driver_name,json=driverName,proto3" json:"driver_name,omitempty"` + // Driver-reported implementation version from the startup capability snapshot. + DriverVersion string `protobuf:"bytes,2,opt,name=driver_version,json=driverVersion,proto3" json:"driver_version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ComputeDriverCapabilities) Reset() { + *x = ComputeDriverCapabilities{} + mi := &file_openshell_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ComputeDriverCapabilities) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ComputeDriverCapabilities) ProtoMessage() {} + +func (x *ComputeDriverCapabilities) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ComputeDriverCapabilities.ProtoReflect.Descriptor instead. +func (*ComputeDriverCapabilities) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{11} +} + +func (x *ComputeDriverCapabilities) GetDriverName() string { + if x != nil { + return x.DriverName + } + return "" +} + +func (x *ComputeDriverCapabilities) GetDriverVersion() string { + if x != nil { + return x.DriverVersion + } + return "" +} + +// 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. +type Sandbox struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Kubernetes-style metadata (id, name, labels, timestamps, resource version). + Metadata *datamodelv1.ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + // Desired sandbox configuration submitted through the API. + Spec *SandboxSpec `protobuf:"bytes,2,opt,name=spec,proto3" json:"spec,omitempty"` + // Latest user-facing observed status derived by the gateway. + Status *SandboxStatus `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Sandbox) Reset() { + *x = Sandbox{} + mi := &file_openshell_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Sandbox) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Sandbox) ProtoMessage() {} + +func (x *Sandbox) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Sandbox.ProtoReflect.Descriptor instead. +func (*Sandbox) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{12} +} + +func (x *Sandbox) GetMetadata() *datamodelv1.ObjectMeta { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *Sandbox) GetSpec() *SandboxSpec { + if x != nil { + return x.Spec + } + return nil +} + +func (x *Sandbox) GetStatus() *SandboxStatus { + if x != nil { + return x.Status + } + return nil +} + +// Desired sandbox configuration provided through the public API. +type SandboxSpec struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Log level exposed to processes running inside the sandbox. + LogLevel string `protobuf:"bytes,1,opt,name=log_level,json=logLevel,proto3" json:"log_level,omitempty"` + // Environment variables injected into the sandbox runtime. + Environment map[string]string `protobuf:"bytes,5,rep,name=environment,proto3" json:"environment,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Container or VM template used to provision the sandbox. + Template *SandboxTemplate `protobuf:"bytes,6,opt,name=template,proto3" json:"template,omitempty"` + // Required sandbox policy configuration. + Policy *sandboxv1.SandboxPolicy `protobuf:"bytes,7,opt,name=policy,proto3" json:"policy,omitempty"` + // Provider names to attach to this sandbox. + Providers []string `protobuf:"bytes,8,rep,name=providers,proto3" json:"providers,omitempty"` + // Portable resource requirements used by the gateway for driver selection + // and by drivers for provisioning. + ResourceRequirements *ResourceRequirements `protobuf:"bytes,9,opt,name=resource_requirements,json=resourceRequirements,proto3" json:"resource_requirements,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxSpec) Reset() { + *x = SandboxSpec{} + mi := &file_openshell_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxSpec) ProtoMessage() {} + +func (x *SandboxSpec) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxSpec.ProtoReflect.Descriptor instead. +func (*SandboxSpec) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{13} +} + +func (x *SandboxSpec) GetLogLevel() string { + if x != nil { + return x.LogLevel + } + return "" +} + +func (x *SandboxSpec) GetEnvironment() map[string]string { + if x != nil { + return x.Environment + } + return nil +} + +func (x *SandboxSpec) GetTemplate() *SandboxTemplate { + if x != nil { + return x.Template + } + return nil +} + +func (x *SandboxSpec) GetPolicy() *sandboxv1.SandboxPolicy { + if x != nil { + return x.Policy + } + return nil +} + +func (x *SandboxSpec) GetProviders() []string { + if x != nil { + return x.Providers + } + return nil +} + +func (x *SandboxSpec) GetResourceRequirements() *ResourceRequirements { + if x != nil { + return x.ResourceRequirements + } + return nil +} + +type ResourceRequirements struct { + state protoimpl.MessageState `protogen:"open.v1"` + // GPU requirements for the sandbox. Presence indicates a GPU request. + Gpu *GpuResourceRequirements `protobuf:"bytes,1,opt,name=gpu,proto3" json:"gpu,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResourceRequirements) Reset() { + *x = ResourceRequirements{} + mi := &file_openshell_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResourceRequirements) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceRequirements) ProtoMessage() {} + +func (x *ResourceRequirements) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceRequirements.ProtoReflect.Descriptor instead. +func (*ResourceRequirements) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{14} +} + +func (x *ResourceRequirements) GetGpu() *GpuResourceRequirements { + if x != nil { + return x.Gpu + } + return nil +} + +// Public GPU resource requirements. +type GpuResourceRequirements struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Optional number of GPUs requested. When omitted, the request is for one + // GPU using the selected driver's default assignment behavior. + Count *uint32 `protobuf:"varint,1,opt,name=count,proto3,oneof" json:"count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GpuResourceRequirements) Reset() { + *x = GpuResourceRequirements{} + mi := &file_openshell_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GpuResourceRequirements) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GpuResourceRequirements) ProtoMessage() {} + +func (x *GpuResourceRequirements) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GpuResourceRequirements.ProtoReflect.Descriptor instead. +func (*GpuResourceRequirements) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{15} +} + +func (x *GpuResourceRequirements) GetCount() uint32 { + if x != nil && x.Count != nil { + return *x.Count + } + return 0 +} + +// Public sandbox template mapped onto compute-driver template inputs. +type SandboxTemplate struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Fully-qualified OCI image reference used to boot the sandbox. + Image string `protobuf:"bytes,1,opt,name=image,proto3" json:"image,omitempty"` + // Optional runtime class name requested from the compute platform. + RuntimeClassName string `protobuf:"bytes,2,opt,name=runtime_class_name,json=runtimeClassName,proto3" json:"runtime_class_name,omitempty"` + // Optional agent socket path exposed to the workload. + AgentSocket string `protobuf:"bytes,3,opt,name=agent_socket,json=agentSocket,proto3" json:"agent_socket,omitempty"` + // Labels applied to compute-platform resources for this sandbox. + Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Annotations applied to compute-platform resources for this sandbox. + Annotations map[string]string `protobuf:"bytes,5,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Additional environment variables injected by the template. + Environment map[string]string `protobuf:"bytes,6,rep,name=environment,proto3" json:"environment,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Platform-specific compute resource requirements and limits. + Resources *structpb.Struct `protobuf:"bytes,7,opt,name=resources,proto3" json:"resources,omitempty"` + // 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. + UserNamespaces *bool `protobuf:"varint,10,opt,name=user_namespaces,json=userNamespaces,proto3,oneof" json:"user_namespaces,omitempty"` + // 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. + DriverConfig *structpb.Struct `protobuf:"bytes,11,opt,name=driver_config,json=driverConfig,proto3" json:"driver_config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxTemplate) Reset() { + *x = SandboxTemplate{} + mi := &file_openshell_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxTemplate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxTemplate) ProtoMessage() {} + +func (x *SandboxTemplate) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxTemplate.ProtoReflect.Descriptor instead. +func (*SandboxTemplate) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{16} +} + +func (x *SandboxTemplate) GetImage() string { + if x != nil { + return x.Image + } + return "" +} + +func (x *SandboxTemplate) GetRuntimeClassName() string { + if x != nil { + return x.RuntimeClassName + } + return "" +} + +func (x *SandboxTemplate) GetAgentSocket() string { + if x != nil { + return x.AgentSocket + } + return "" +} + +func (x *SandboxTemplate) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +func (x *SandboxTemplate) GetAnnotations() map[string]string { + if x != nil { + return x.Annotations + } + return nil +} + +func (x *SandboxTemplate) GetEnvironment() map[string]string { + if x != nil { + return x.Environment + } + return nil +} + +func (x *SandboxTemplate) GetResources() *structpb.Struct { + if x != nil { + return x.Resources + } + return nil +} + +func (x *SandboxTemplate) GetUserNamespaces() bool { + if x != nil && x.UserNamespaces != nil { + return *x.UserNamespaces + } + return false +} + +func (x *SandboxTemplate) GetDriverConfig() *structpb.Struct { + if x != nil { + return x.DriverConfig + } + return nil +} + +// User-facing sandbox status derived by the gateway from compute-driver observations. +// +// Public status does not embed driver-only flags such as `deleting`. +type SandboxStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Compute-platform sandbox object name. + SandboxName string `protobuf:"bytes,1,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` + // Name of the agent pod or equivalent runtime instance. + AgentPod string `protobuf:"bytes,2,opt,name=agent_pod,json=agentPod,proto3" json:"agent_pod,omitempty"` + // File descriptor or endpoint for reaching the agent service, when available. + AgentFd string `protobuf:"bytes,3,opt,name=agent_fd,json=agentFd,proto3" json:"agent_fd,omitempty"` + // File descriptor or endpoint for reaching the sandbox service, when available. + SandboxFd string `protobuf:"bytes,4,opt,name=sandbox_fd,json=sandboxFd,proto3" json:"sandbox_fd,omitempty"` + // Latest user-facing readiness and lifecycle conditions. + Conditions []*SandboxCondition `protobuf:"bytes,5,rep,name=conditions,proto3" json:"conditions,omitempty"` + // Gateway-derived lifecycle summary. + Phase SandboxPhase `protobuf:"varint,6,opt,name=phase,proto3,enum=openshell.v1.SandboxPhase" json:"phase,omitempty"` + // Currently active policy version (updated when sandbox reports loaded). + CurrentPolicyVersion uint32 `protobuf:"varint,7,opt,name=current_policy_version,json=currentPolicyVersion,proto3" json:"current_policy_version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxStatus) Reset() { + *x = SandboxStatus{} + mi := &file_openshell_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxStatus) ProtoMessage() {} + +func (x *SandboxStatus) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxStatus.ProtoReflect.Descriptor instead. +func (*SandboxStatus) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{17} +} + +func (x *SandboxStatus) GetSandboxName() string { + if x != nil { + return x.SandboxName + } + return "" +} + +func (x *SandboxStatus) GetAgentPod() string { + if x != nil { + return x.AgentPod + } + return "" +} + +func (x *SandboxStatus) GetAgentFd() string { + if x != nil { + return x.AgentFd + } + return "" +} + +func (x *SandboxStatus) GetSandboxFd() string { + if x != nil { + return x.SandboxFd + } + return "" +} + +func (x *SandboxStatus) GetConditions() []*SandboxCondition { + if x != nil { + return x.Conditions + } + return nil +} + +func (x *SandboxStatus) GetPhase() SandboxPhase { + if x != nil { + return x.Phase + } + return SandboxPhase_SANDBOX_PHASE_UNSPECIFIED +} + +func (x *SandboxStatus) GetCurrentPolicyVersion() uint32 { + if x != nil { + return x.CurrentPolicyVersion + } + return 0 +} + +// User-facing sandbox condition derived from driver-native conditions. +type SandboxCondition struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Condition class, typically mirroring the underlying platform condition type. + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + // Condition status value such as `True`, `False`, or `Unknown`. + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + // Short machine-readable reason associated with the condition. + Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` + // Human-readable condition message. + Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` + // Timestamp reported by the underlying platform for the last transition. + LastTransitionTime string `protobuf:"bytes,5,opt,name=last_transition_time,json=lastTransitionTime,proto3" json:"last_transition_time,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxCondition) Reset() { + *x = SandboxCondition{} + mi := &file_openshell_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxCondition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxCondition) ProtoMessage() {} + +func (x *SandboxCondition) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxCondition.ProtoReflect.Descriptor instead. +func (*SandboxCondition) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{18} +} + +func (x *SandboxCondition) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *SandboxCondition) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *SandboxCondition) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *SandboxCondition) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *SandboxCondition) GetLastTransitionTime() string { + if x != nil { + return x.LastTransitionTime + } + return "" +} + +// Public platform event exposed on the sandbox watch stream. +type PlatformEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Event timestamp in milliseconds since epoch. + TimestampMs int64 `protobuf:"varint,1,opt,name=timestamp_ms,json=timestampMs,proto3" json:"timestamp_ms,omitempty"` + // Event source (e.g. "kubernetes", "docker", "process"). + Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"` + // Event type/severity (e.g. "Normal", "Warning"). + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + // Short reason code (e.g. "Started", "Pulled", "Failed"). + Reason string `protobuf:"bytes,4,opt,name=reason,proto3" json:"reason,omitempty"` + // Human-readable event message. + Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` + // Optional metadata as key-value pairs. + Metadata map[string]string `protobuf:"bytes,6,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PlatformEvent) Reset() { + *x = PlatformEvent{} + mi := &file_openshell_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PlatformEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PlatformEvent) ProtoMessage() {} + +func (x *PlatformEvent) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PlatformEvent.ProtoReflect.Descriptor instead. +func (*PlatformEvent) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{19} +} + +func (x *PlatformEvent) GetTimestampMs() int64 { + if x != nil { + return x.TimestampMs + } + return 0 +} + +func (x *PlatformEvent) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *PlatformEvent) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *PlatformEvent) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *PlatformEvent) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *PlatformEvent) GetMetadata() map[string]string { + if x != nil { + return x.Metadata + } + return nil +} + +// Create sandbox request. +type CreateSandboxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Spec *SandboxSpec `protobuf:"bytes,1,opt,name=spec,proto3" json:"spec,omitempty"` + // Optional user-supplied sandbox name. When empty the server generates one. + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // Optional labels for the sandbox (key-value metadata). + Labels map[string]string `protobuf:"bytes,3,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Optional annotations for the sandbox (non-selector metadata). + Annotations map[string]string `protobuf:"bytes,4,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Workspace for the sandbox. Empty defaults to "default". + Workspace string `protobuf:"bytes,5,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateSandboxRequest) Reset() { + *x = CreateSandboxRequest{} + mi := &file_openshell_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateSandboxRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateSandboxRequest) ProtoMessage() {} + +func (x *CreateSandboxRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateSandboxRequest.ProtoReflect.Descriptor instead. +func (*CreateSandboxRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{20} +} + +func (x *CreateSandboxRequest) GetSpec() *SandboxSpec { + if x != nil { + return x.Spec + } + return nil +} + +func (x *CreateSandboxRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateSandboxRequest) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +func (x *CreateSandboxRequest) GetAnnotations() map[string]string { + if x != nil { + return x.Annotations + } + return nil +} + +func (x *CreateSandboxRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Get sandbox request. +type GetSandboxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name (canonical lookup key). + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSandboxRequest) Reset() { + *x = GetSandboxRequest{} + mi := &file_openshell_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSandboxRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSandboxRequest) ProtoMessage() {} + +func (x *GetSandboxRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSandboxRequest.ProtoReflect.Descriptor instead. +func (*GetSandboxRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{21} +} + +func (x *GetSandboxRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetSandboxRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// List sandboxes request. +type ListSandboxesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + // Optional label selector for filtering (format: "key1=value1,key2=value2"). + LabelSelector string `protobuf:"bytes,3,opt,name=label_selector,json=labelSelector,proto3" json:"label_selector,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` + // List across all workspaces. Mutually exclusive with workspace. + AllWorkspaces bool `protobuf:"varint,5,opt,name=all_workspaces,json=allWorkspaces,proto3" json:"all_workspaces,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSandboxesRequest) Reset() { + *x = ListSandboxesRequest{} + mi := &file_openshell_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSandboxesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSandboxesRequest) ProtoMessage() {} + +func (x *ListSandboxesRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSandboxesRequest.ProtoReflect.Descriptor instead. +func (*ListSandboxesRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{22} +} + +func (x *ListSandboxesRequest) GetLimit() uint32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListSandboxesRequest) GetOffset() uint32 { + if x != nil { + return x.Offset + } + return 0 +} + +func (x *ListSandboxesRequest) GetLabelSelector() string { + if x != nil { + return x.LabelSelector + } + return "" +} + +func (x *ListSandboxesRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +func (x *ListSandboxesRequest) GetAllWorkspaces() bool { + if x != nil { + return x.AllWorkspaces + } + return false +} + +// List providers attached to a sandbox request. +type ListSandboxProvidersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name (canonical lookup key). + SandboxName string `protobuf:"bytes,1,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSandboxProvidersRequest) Reset() { + *x = ListSandboxProvidersRequest{} + mi := &file_openshell_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSandboxProvidersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSandboxProvidersRequest) ProtoMessage() {} + +func (x *ListSandboxProvidersRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSandboxProvidersRequest.ProtoReflect.Descriptor instead. +func (*ListSandboxProvidersRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{23} +} + +func (x *ListSandboxProvidersRequest) GetSandboxName() string { + if x != nil { + return x.SandboxName + } + return "" +} + +func (x *ListSandboxProvidersRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Attach provider to sandbox request. +type AttachSandboxProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name (canonical lookup key). + SandboxName string `protobuf:"bytes,1,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` + // Provider name to attach. + ProviderName string `protobuf:"bytes,2,opt,name=provider_name,json=providerName,proto3" json:"provider_name,omitempty"` + // Expected resource version for optimistic concurrency control. + // If 0, the server uses the current version (backward compatibility). + // If non-zero, the server validates that the sandbox's current resource_version + // matches this value before applying the mutation, returning ABORTED on mismatch. + ExpectedResourceVersion uint64 `protobuf:"varint,3,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AttachSandboxProviderRequest) Reset() { + *x = AttachSandboxProviderRequest{} + mi := &file_openshell_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AttachSandboxProviderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttachSandboxProviderRequest) ProtoMessage() {} + +func (x *AttachSandboxProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttachSandboxProviderRequest.ProtoReflect.Descriptor instead. +func (*AttachSandboxProviderRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{24} +} + +func (x *AttachSandboxProviderRequest) GetSandboxName() string { + if x != nil { + return x.SandboxName + } + return "" +} + +func (x *AttachSandboxProviderRequest) GetProviderName() string { + if x != nil { + return x.ProviderName + } + return "" +} + +func (x *AttachSandboxProviderRequest) GetExpectedResourceVersion() uint64 { + if x != nil { + return x.ExpectedResourceVersion + } + return 0 +} + +func (x *AttachSandboxProviderRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Detach provider from sandbox request. +type DetachSandboxProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name (canonical lookup key). + SandboxName string `protobuf:"bytes,1,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` + // Provider name to detach. + ProviderName string `protobuf:"bytes,2,opt,name=provider_name,json=providerName,proto3" json:"provider_name,omitempty"` + // Expected resource version for optimistic concurrency control. + // If 0, the server uses the current version (backward compatibility). + // If non-zero, the server validates that the sandbox's current resource_version + // matches this value before applying the mutation, returning ABORTED on mismatch. + ExpectedResourceVersion uint64 `protobuf:"varint,3,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DetachSandboxProviderRequest) Reset() { + *x = DetachSandboxProviderRequest{} + mi := &file_openshell_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DetachSandboxProviderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DetachSandboxProviderRequest) ProtoMessage() {} + +func (x *DetachSandboxProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DetachSandboxProviderRequest.ProtoReflect.Descriptor instead. +func (*DetachSandboxProviderRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{25} +} + +func (x *DetachSandboxProviderRequest) GetSandboxName() string { + if x != nil { + return x.SandboxName + } + return "" +} + +func (x *DetachSandboxProviderRequest) GetProviderName() string { + if x != nil { + return x.ProviderName + } + return "" +} + +func (x *DetachSandboxProviderRequest) GetExpectedResourceVersion() uint64 { + if x != nil { + return x.ExpectedResourceVersion + } + return 0 +} + +func (x *DetachSandboxProviderRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Delete sandbox request. +type DeleteSandboxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name (canonical lookup key). + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteSandboxRequest) Reset() { + *x = DeleteSandboxRequest{} + mi := &file_openshell_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteSandboxRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteSandboxRequest) ProtoMessage() {} + +func (x *DeleteSandboxRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteSandboxRequest.ProtoReflect.Descriptor instead. +func (*DeleteSandboxRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{26} +} + +func (x *DeleteSandboxRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DeleteSandboxRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Sandbox response. +type SandboxResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxResponse) Reset() { + *x = SandboxResponse{} + mi := &file_openshell_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxResponse) ProtoMessage() {} + +func (x *SandboxResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxResponse.ProtoReflect.Descriptor instead. +func (*SandboxResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{27} +} + +func (x *SandboxResponse) GetSandbox() *Sandbox { + if x != nil { + return x.Sandbox + } + return nil +} + +// List sandboxes response. +type ListSandboxesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sandboxes []*Sandbox `protobuf:"bytes,1,rep,name=sandboxes,proto3" json:"sandboxes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSandboxesResponse) Reset() { + *x = ListSandboxesResponse{} + mi := &file_openshell_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSandboxesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSandboxesResponse) ProtoMessage() {} + +func (x *ListSandboxesResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSandboxesResponse.ProtoReflect.Descriptor instead. +func (*ListSandboxesResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{28} +} + +func (x *ListSandboxesResponse) GetSandboxes() []*Sandbox { + if x != nil { + return x.Sandboxes + } + return nil +} + +// List providers attached to a sandbox response. +type ListSandboxProvidersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Providers []*datamodelv1.Provider `protobuf:"bytes,1,rep,name=providers,proto3" json:"providers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSandboxProvidersResponse) Reset() { + *x = ListSandboxProvidersResponse{} + mi := &file_openshell_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSandboxProvidersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSandboxProvidersResponse) ProtoMessage() {} + +func (x *ListSandboxProvidersResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSandboxProvidersResponse.ProtoReflect.Descriptor instead. +func (*ListSandboxProvidersResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{29} +} + +func (x *ListSandboxProvidersResponse) GetProviders() []*datamodelv1.Provider { + if x != nil { + return x.Providers + } + return nil +} + +// Attach provider to sandbox response. +type AttachSandboxProviderResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // True when the provider was newly attached. False means it was already attached. + Attached bool `protobuf:"varint,2,opt,name=attached,proto3" json:"attached,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AttachSandboxProviderResponse) Reset() { + *x = AttachSandboxProviderResponse{} + mi := &file_openshell_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AttachSandboxProviderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttachSandboxProviderResponse) ProtoMessage() {} + +func (x *AttachSandboxProviderResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttachSandboxProviderResponse.ProtoReflect.Descriptor instead. +func (*AttachSandboxProviderResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{30} +} + +func (x *AttachSandboxProviderResponse) GetSandbox() *Sandbox { + if x != nil { + return x.Sandbox + } + return nil +} + +func (x *AttachSandboxProviderResponse) GetAttached() bool { + if x != nil { + return x.Attached + } + return false +} + +// Detach provider from sandbox response. +type DetachSandboxProviderResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // True when the provider was removed. False means it was not attached. + Detached bool `protobuf:"varint,2,opt,name=detached,proto3" json:"detached,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DetachSandboxProviderResponse) Reset() { + *x = DetachSandboxProviderResponse{} + mi := &file_openshell_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DetachSandboxProviderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DetachSandboxProviderResponse) ProtoMessage() {} + +func (x *DetachSandboxProviderResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DetachSandboxProviderResponse.ProtoReflect.Descriptor instead. +func (*DetachSandboxProviderResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{31} +} + +func (x *DetachSandboxProviderResponse) GetSandbox() *Sandbox { + if x != nil { + return x.Sandbox + } + return nil +} + +func (x *DetachSandboxProviderResponse) GetDetached() bool { + if x != nil { + return x.Detached + } + return false +} + +// Delete sandbox response. +type DeleteSandboxResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteSandboxResponse) Reset() { + *x = DeleteSandboxResponse{} + mi := &file_openshell_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteSandboxResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteSandboxResponse) ProtoMessage() {} + +func (x *DeleteSandboxResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteSandboxResponse.ProtoReflect.Descriptor instead. +func (*DeleteSandboxResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{32} +} + +func (x *DeleteSandboxResponse) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + +// Create SSH session request. +type CreateSshSessionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox id. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateSshSessionRequest) Reset() { + *x = CreateSshSessionRequest{} + mi := &file_openshell_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateSshSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateSshSessionRequest) ProtoMessage() {} + +func (x *CreateSshSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[33] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateSshSessionRequest.ProtoReflect.Descriptor instead. +func (*CreateSshSessionRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{33} +} + +func (x *CreateSshSessionRequest) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +// 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. +type CreateSshSessionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox id. [A-Za-z0-9._-]{1,128}. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Session token for the gateway tunnel. URL-safe ASCII + // ([A-Za-z0-9._~+/=-]) up to 4096 bytes. No shell metacharacters or + // whitespace. + Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` + // 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. + GatewayHost string `protobuf:"bytes,3,opt,name=gateway_host,json=gatewayHost,proto3" json:"gateway_host,omitempty"` + // Gateway port for SSH proxy connection. Must be in range 1..=65535. + GatewayPort uint32 `protobuf:"varint,4,opt,name=gateway_port,json=gatewayPort,proto3" json:"gateway_port,omitempty"` + // Gateway scheme. Must be exactly "http" or "https". + GatewayScheme string `protobuf:"bytes,5,opt,name=gateway_scheme,json=gatewayScheme,proto3" json:"gateway_scheme,omitempty"` + // Optional host key fingerprint. If non-empty, [A-Za-z0-9:+/=-] only. + HostKeyFingerprint string `protobuf:"bytes,7,opt,name=host_key_fingerprint,json=hostKeyFingerprint,proto3" json:"host_key_fingerprint,omitempty"` + // Expiry timestamp in milliseconds since epoch. 0 means no expiry. + ExpiresAtMs int64 `protobuf:"varint,8,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateSshSessionResponse) Reset() { + *x = CreateSshSessionResponse{} + mi := &file_openshell_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateSshSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateSshSessionResponse) ProtoMessage() {} + +func (x *CreateSshSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[34] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateSshSessionResponse.ProtoReflect.Descriptor instead. +func (*CreateSshSessionResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{34} +} + +func (x *CreateSshSessionResponse) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *CreateSshSessionResponse) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *CreateSshSessionResponse) GetGatewayHost() string { + if x != nil { + return x.GatewayHost + } + return "" +} + +func (x *CreateSshSessionResponse) GetGatewayPort() uint32 { + if x != nil { + return x.GatewayPort + } + return 0 +} + +func (x *CreateSshSessionResponse) GetGatewayScheme() string { + if x != nil { + return x.GatewayScheme + } + return "" +} + +func (x *CreateSshSessionResponse) GetHostKeyFingerprint() string { + if x != nil { + return x.HostKeyFingerprint + } + return "" +} + +func (x *CreateSshSessionResponse) GetExpiresAtMs() int64 { + if x != nil { + return x.ExpiresAtMs + } + return 0 +} + +// Request to expose an HTTP service running inside a sandbox. +type ExposeServiceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name. + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Service name within the sandbox. + Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` + // Loopback TCP port inside the sandbox. + TargetPort uint32 `protobuf:"varint,3,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"` + // Whether to print/use the browser-facing service URL. + Domain bool `protobuf:"varint,4,opt,name=domain,proto3" json:"domain,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,5,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExposeServiceRequest) Reset() { + *x = ExposeServiceRequest{} + mi := &file_openshell_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExposeServiceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExposeServiceRequest) ProtoMessage() {} + +func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[35] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExposeServiceRequest.ProtoReflect.Descriptor instead. +func (*ExposeServiceRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{35} +} + +func (x *ExposeServiceRequest) GetSandbox() string { + if x != nil { + return x.Sandbox + } + return "" +} + +func (x *ExposeServiceRequest) GetService() string { + if x != nil { + return x.Service + } + return "" +} + +func (x *ExposeServiceRequest) GetTargetPort() uint32 { + if x != nil { + return x.TargetPort + } + return 0 +} + +func (x *ExposeServiceRequest) GetDomain() bool { + if x != nil { + return x.Domain + } + return false +} + +func (x *ExposeServiceRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Request to fetch an exposed sandbox service endpoint. +type GetServiceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name. + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Service name within the sandbox. Empty selects the unnamed endpoint. + Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetServiceRequest) Reset() { + *x = GetServiceRequest{} + mi := &file_openshell_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetServiceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetServiceRequest) ProtoMessage() {} + +func (x *GetServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[36] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetServiceRequest.ProtoReflect.Descriptor instead. +func (*GetServiceRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{36} +} + +func (x *GetServiceRequest) GetSandbox() string { + if x != nil { + return x.Sandbox + } + return "" +} + +func (x *GetServiceRequest) GetService() string { + if x != nil { + return x.Service + } + return "" +} + +func (x *GetServiceRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Request to list exposed sandbox service endpoints. +type ListServicesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Optional sandbox name. Empty lists endpoints for all sandboxes. + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Page size. Zero uses the server default. + Limit uint32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + // Page offset. + Offset uint32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` + // List across all workspaces. Mutually exclusive with workspace. + AllWorkspaces bool `protobuf:"varint,5,opt,name=all_workspaces,json=allWorkspaces,proto3" json:"all_workspaces,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListServicesRequest) Reset() { + *x = ListServicesRequest{} + mi := &file_openshell_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListServicesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListServicesRequest) ProtoMessage() {} + +func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[37] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListServicesRequest.ProtoReflect.Descriptor instead. +func (*ListServicesRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{37} +} + +func (x *ListServicesRequest) GetSandbox() string { + if x != nil { + return x.Sandbox + } + return "" +} + +func (x *ListServicesRequest) GetLimit() uint32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListServicesRequest) GetOffset() uint32 { + if x != nil { + return x.Offset + } + return 0 +} + +func (x *ListServicesRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +func (x *ListServicesRequest) GetAllWorkspaces() bool { + if x != nil { + return x.AllWorkspaces + } + return false +} + +// Response containing exposed sandbox service endpoints. +type ListServicesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Services []*ServiceEndpointResponse `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListServicesResponse) Reset() { + *x = ListServicesResponse{} + mi := &file_openshell_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListServicesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListServicesResponse) ProtoMessage() {} + +func (x *ListServicesResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[38] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListServicesResponse.ProtoReflect.Descriptor instead. +func (*ListServicesResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{38} +} + +func (x *ListServicesResponse) GetServices() []*ServiceEndpointResponse { + if x != nil { + return x.Services + } + return nil +} + +// Request to delete an exposed sandbox service endpoint. +type DeleteServiceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name. + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Service name within the sandbox. Empty selects the unnamed endpoint. + Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteServiceRequest) Reset() { + *x = DeleteServiceRequest{} + mi := &file_openshell_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteServiceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteServiceRequest) ProtoMessage() {} + +func (x *DeleteServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[39] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteServiceRequest.ProtoReflect.Descriptor instead. +func (*DeleteServiceRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{39} +} + +func (x *DeleteServiceRequest) GetSandbox() string { + if x != nil { + return x.Sandbox + } + return "" +} + +func (x *DeleteServiceRequest) GetService() string { + if x != nil { + return x.Service + } + return "" +} + +func (x *DeleteServiceRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Response for deleting an exposed sandbox service endpoint. +type DeleteServiceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // True when an endpoint existed and was deleted. + Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteServiceResponse) Reset() { + *x = DeleteServiceResponse{} + mi := &file_openshell_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteServiceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteServiceResponse) ProtoMessage() {} + +func (x *DeleteServiceResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[40] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteServiceResponse.ProtoReflect.Descriptor instead. +func (*DeleteServiceResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{40} +} + +func (x *DeleteServiceResponse) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + +// Persisted sandbox service endpoint. +type ServiceEndpoint struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Kubernetes-style metadata. + Metadata *datamodelv1.ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + // Sandbox object ID. + SandboxId string `protobuf:"bytes,2,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Sandbox name. + SandboxName string `protobuf:"bytes,3,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` + // Service name within the sandbox. + ServiceName string `protobuf:"bytes,4,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // Loopback TCP port inside the sandbox. + TargetPort uint32 `protobuf:"varint,5,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"` + // Whether browser-facing service routing is enabled for this endpoint. + Domain bool `protobuf:"varint,6,opt,name=domain,proto3" json:"domain,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ServiceEndpoint) Reset() { + *x = ServiceEndpoint{} + mi := &file_openshell_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ServiceEndpoint) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServiceEndpoint) ProtoMessage() {} + +func (x *ServiceEndpoint) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[41] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServiceEndpoint.ProtoReflect.Descriptor instead. +func (*ServiceEndpoint) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{41} +} + +func (x *ServiceEndpoint) GetMetadata() *datamodelv1.ObjectMeta { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *ServiceEndpoint) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *ServiceEndpoint) GetSandboxName() string { + if x != nil { + return x.SandboxName + } + return "" +} + +func (x *ServiceEndpoint) GetServiceName() string { + if x != nil { + return x.ServiceName + } + return "" +} + +func (x *ServiceEndpoint) GetTargetPort() uint32 { + if x != nil { + return x.TargetPort + } + return 0 +} + +func (x *ServiceEndpoint) GetDomain() bool { + if x != nil { + return x.Domain + } + return false +} + +// Response containing a service endpoint and, when available, its local URL. +type ServiceEndpointResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Endpoint *ServiceEndpoint `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ServiceEndpointResponse) Reset() { + *x = ServiceEndpointResponse{} + mi := &file_openshell_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ServiceEndpointResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServiceEndpointResponse) ProtoMessage() {} + +func (x *ServiceEndpointResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[42] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServiceEndpointResponse.ProtoReflect.Descriptor instead. +func (*ServiceEndpointResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{42} +} + +func (x *ServiceEndpointResponse) GetEndpoint() *ServiceEndpoint { + if x != nil { + return x.Endpoint + } + return nil +} + +func (x *ServiceEndpointResponse) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +// Revoke SSH session request. +type RevokeSshSessionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Session token to revoke. + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RevokeSshSessionRequest) Reset() { + *x = RevokeSshSessionRequest{} + mi := &file_openshell_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RevokeSshSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeSshSessionRequest) ProtoMessage() {} + +func (x *RevokeSshSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[43] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeSshSessionRequest.ProtoReflect.Descriptor instead. +func (*RevokeSshSessionRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{43} +} + +func (x *RevokeSshSessionRequest) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +// Revoke SSH session response. +type RevokeSshSessionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // True when a session was revoked. + Revoked bool `protobuf:"varint,1,opt,name=revoked,proto3" json:"revoked,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RevokeSshSessionResponse) Reset() { + *x = RevokeSshSessionResponse{} + mi := &file_openshell_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RevokeSshSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeSshSessionResponse) ProtoMessage() {} + +func (x *RevokeSshSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[44] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeSshSessionResponse.ProtoReflect.Descriptor instead. +func (*RevokeSshSessionResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{44} +} + +func (x *RevokeSshSessionResponse) GetRevoked() bool { + if x != nil { + return x.Revoked + } + return false +} + +// Execute command request. +type ExecSandboxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox id. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Command and arguments. + Command []string `protobuf:"bytes,2,rep,name=command,proto3" json:"command,omitempty"` + // Optional working directory. + Workdir string `protobuf:"bytes,3,opt,name=workdir,proto3" json:"workdir,omitempty"` + // Optional environment overrides. + Environment map[string]string `protobuf:"bytes,4,rep,name=environment,proto3" json:"environment,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Optional timeout in seconds. 0 means no timeout. + TimeoutSeconds uint32 `protobuf:"varint,5,opt,name=timeout_seconds,json=timeoutSeconds,proto3" json:"timeout_seconds,omitempty"` + // Optional stdin payload passed to the command. + Stdin []byte `protobuf:"bytes,6,opt,name=stdin,proto3" json:"stdin,omitempty"` + // Request a pseudo-terminal for the remote command. + Tty bool `protobuf:"varint,7,opt,name=tty,proto3" json:"tty,omitempty"` + // Initial terminal columns (used when tty=true, 0 = use default). + Cols uint32 `protobuf:"varint,8,opt,name=cols,proto3" json:"cols,omitempty"` + // Initial terminal rows (used when tty=true, 0 = use default). + Rows uint32 `protobuf:"varint,9,opt,name=rows,proto3" json:"rows,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecSandboxRequest) Reset() { + *x = ExecSandboxRequest{} + mi := &file_openshell_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecSandboxRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecSandboxRequest) ProtoMessage() {} + +func (x *ExecSandboxRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[45] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecSandboxRequest.ProtoReflect.Descriptor instead. +func (*ExecSandboxRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{45} +} + +func (x *ExecSandboxRequest) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *ExecSandboxRequest) GetCommand() []string { + if x != nil { + return x.Command + } + return nil +} + +func (x *ExecSandboxRequest) GetWorkdir() string { + if x != nil { + return x.Workdir + } + return "" +} + +func (x *ExecSandboxRequest) GetEnvironment() map[string]string { + if x != nil { + return x.Environment + } + return nil +} + +func (x *ExecSandboxRequest) GetTimeoutSeconds() uint32 { + if x != nil { + return x.TimeoutSeconds + } + return 0 +} + +func (x *ExecSandboxRequest) GetStdin() []byte { + if x != nil { + return x.Stdin + } + return nil +} + +func (x *ExecSandboxRequest) GetTty() bool { + if x != nil { + return x.Tty + } + return false +} + +func (x *ExecSandboxRequest) GetCols() uint32 { + if x != nil { + return x.Cols + } + return 0 +} + +func (x *ExecSandboxRequest) GetRows() uint32 { + if x != nil { + return x.Rows + } + return 0 +} + +// One stdout chunk from a sandbox exec. +type ExecSandboxStdout struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecSandboxStdout) Reset() { + *x = ExecSandboxStdout{} + mi := &file_openshell_proto_msgTypes[46] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecSandboxStdout) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecSandboxStdout) ProtoMessage() {} + +func (x *ExecSandboxStdout) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[46] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecSandboxStdout.ProtoReflect.Descriptor instead. +func (*ExecSandboxStdout) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{46} +} + +func (x *ExecSandboxStdout) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +// One stderr chunk from a sandbox exec. +type ExecSandboxStderr struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecSandboxStderr) Reset() { + *x = ExecSandboxStderr{} + mi := &file_openshell_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecSandboxStderr) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecSandboxStderr) ProtoMessage() {} + +func (x *ExecSandboxStderr) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[47] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecSandboxStderr.ProtoReflect.Descriptor instead. +func (*ExecSandboxStderr) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{47} +} + +func (x *ExecSandboxStderr) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +// Final exit status for a sandbox exec. +type ExecSandboxExit struct { + state protoimpl.MessageState `protogen:"open.v1"` + ExitCode int32 `protobuf:"varint,1,opt,name=exit_code,json=exitCode,proto3" json:"exit_code,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecSandboxExit) Reset() { + *x = ExecSandboxExit{} + mi := &file_openshell_proto_msgTypes[48] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecSandboxExit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecSandboxExit) ProtoMessage() {} + +func (x *ExecSandboxExit) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[48] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecSandboxExit.ProtoReflect.Descriptor instead. +func (*ExecSandboxExit) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{48} +} + +func (x *ExecSandboxExit) GetExitCode() int32 { + if x != nil { + return x.ExitCode + } + return 0 +} + +// One event in a sandbox exec stream. +type ExecSandboxEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *ExecSandboxEvent_Stdout + // *ExecSandboxEvent_Stderr + // *ExecSandboxEvent_Exit + Payload isExecSandboxEvent_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecSandboxEvent) Reset() { + *x = ExecSandboxEvent{} + mi := &file_openshell_proto_msgTypes[49] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecSandboxEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecSandboxEvent) ProtoMessage() {} + +func (x *ExecSandboxEvent) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[49] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecSandboxEvent.ProtoReflect.Descriptor instead. +func (*ExecSandboxEvent) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{49} +} + +func (x *ExecSandboxEvent) GetPayload() isExecSandboxEvent_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *ExecSandboxEvent) GetStdout() *ExecSandboxStdout { + if x != nil { + if x, ok := x.Payload.(*ExecSandboxEvent_Stdout); ok { + return x.Stdout + } + } + return nil +} + +func (x *ExecSandboxEvent) GetStderr() *ExecSandboxStderr { + if x != nil { + if x, ok := x.Payload.(*ExecSandboxEvent_Stderr); ok { + return x.Stderr + } + } + return nil +} + +func (x *ExecSandboxEvent) GetExit() *ExecSandboxExit { + if x != nil { + if x, ok := x.Payload.(*ExecSandboxEvent_Exit); ok { + return x.Exit + } + } + return nil +} + +type isExecSandboxEvent_Payload interface { + isExecSandboxEvent_Payload() +} + +type ExecSandboxEvent_Stdout struct { + Stdout *ExecSandboxStdout `protobuf:"bytes,1,opt,name=stdout,proto3,oneof"` +} + +type ExecSandboxEvent_Stderr struct { + Stderr *ExecSandboxStderr `protobuf:"bytes,2,opt,name=stderr,proto3,oneof"` +} + +type ExecSandboxEvent_Exit struct { + Exit *ExecSandboxExit `protobuf:"bytes,3,opt,name=exit,proto3,oneof"` +} + +func (*ExecSandboxEvent_Stdout) isExecSandboxEvent_Payload() {} + +func (*ExecSandboxEvent_Stderr) isExecSandboxEvent_Payload() {} + +func (*ExecSandboxEvent_Exit) isExecSandboxEvent_Payload() {} + +// Initial frame for one TCP forward stream. +type TcpForwardInit struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox id. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Optional service identifier for audit/correlation. + ServiceId string `protobuf:"bytes,4,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` + // Target the gateway should request from the supervisor. + // + // Types that are valid to be assigned to Target: + // + // *TcpForwardInit_Ssh + // *TcpForwardInit_Tcp + Target isTcpForwardInit_Target `protobuf_oneof:"target"` + // Optional target-specific authorization token. SSH targets use this as the + // short-lived SSH session token issued by CreateSshSession. + AuthorizationToken string `protobuf:"bytes,7,opt,name=authorization_token,json=authorizationToken,proto3" json:"authorization_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TcpForwardInit) Reset() { + *x = TcpForwardInit{} + mi := &file_openshell_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TcpForwardInit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TcpForwardInit) ProtoMessage() {} + +func (x *TcpForwardInit) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[50] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TcpForwardInit.ProtoReflect.Descriptor instead. +func (*TcpForwardInit) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{50} +} + +func (x *TcpForwardInit) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *TcpForwardInit) GetServiceId() string { + if x != nil { + return x.ServiceId + } + return "" +} + +func (x *TcpForwardInit) GetTarget() isTcpForwardInit_Target { + if x != nil { + return x.Target + } + return nil +} + +func (x *TcpForwardInit) GetSsh() *SshRelayTarget { + if x != nil { + if x, ok := x.Target.(*TcpForwardInit_Ssh); ok { + return x.Ssh + } + } + return nil +} + +func (x *TcpForwardInit) GetTcp() *TcpRelayTarget { + if x != nil { + if x, ok := x.Target.(*TcpForwardInit_Tcp); ok { + return x.Tcp + } + } + return nil +} + +func (x *TcpForwardInit) GetAuthorizationToken() string { + if x != nil { + return x.AuthorizationToken + } + return "" +} + +type isTcpForwardInit_Target interface { + isTcpForwardInit_Target() +} + +type TcpForwardInit_Ssh struct { + Ssh *SshRelayTarget `protobuf:"bytes,5,opt,name=ssh,proto3,oneof"` +} + +type TcpForwardInit_Tcp struct { + Tcp *TcpRelayTarget `protobuf:"bytes,6,opt,name=tcp,proto3,oneof"` +} + +func (*TcpForwardInit_Ssh) isTcpForwardInit_Target() {} + +func (*TcpForwardInit_Tcp) isTcpForwardInit_Target() {} + +// A single frame on the CLI-to-gateway TCP forward stream. +type TcpForwardFrame struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *TcpForwardFrame_Init + // *TcpForwardFrame_Data + Payload isTcpForwardFrame_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TcpForwardFrame) Reset() { + *x = TcpForwardFrame{} + mi := &file_openshell_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TcpForwardFrame) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TcpForwardFrame) ProtoMessage() {} + +func (x *TcpForwardFrame) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[51] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TcpForwardFrame.ProtoReflect.Descriptor instead. +func (*TcpForwardFrame) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{51} +} + +func (x *TcpForwardFrame) GetPayload() isTcpForwardFrame_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *TcpForwardFrame) GetInit() *TcpForwardInit { + if x != nil { + if x, ok := x.Payload.(*TcpForwardFrame_Init); ok { + return x.Init + } + } + return nil +} + +func (x *TcpForwardFrame) GetData() []byte { + if x != nil { + if x, ok := x.Payload.(*TcpForwardFrame_Data); ok { + return x.Data + } + } + return nil +} + +type isTcpForwardFrame_Payload interface { + isTcpForwardFrame_Payload() +} + +type TcpForwardFrame_Init struct { + Init *TcpForwardInit `protobuf:"bytes,1,opt,name=init,proto3,oneof"` +} + +type TcpForwardFrame_Data struct { + Data []byte `protobuf:"bytes,2,opt,name=data,proto3,oneof"` +} + +func (*TcpForwardFrame_Init) isTcpForwardFrame_Payload() {} + +func (*TcpForwardFrame_Data) isTcpForwardFrame_Payload() {} + +// Client-to-server message for interactive exec. +type ExecSandboxInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *ExecSandboxInput_Start + // *ExecSandboxInput_Stdin + // *ExecSandboxInput_Resize + Payload isExecSandboxInput_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecSandboxInput) Reset() { + *x = ExecSandboxInput{} + mi := &file_openshell_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecSandboxInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecSandboxInput) ProtoMessage() {} + +func (x *ExecSandboxInput) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[52] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecSandboxInput.ProtoReflect.Descriptor instead. +func (*ExecSandboxInput) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{52} +} + +func (x *ExecSandboxInput) GetPayload() isExecSandboxInput_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *ExecSandboxInput) GetStart() *ExecSandboxRequest { + if x != nil { + if x, ok := x.Payload.(*ExecSandboxInput_Start); ok { + return x.Start + } + } + return nil +} + +func (x *ExecSandboxInput) GetStdin() []byte { + if x != nil { + if x, ok := x.Payload.(*ExecSandboxInput_Stdin); ok { + return x.Stdin + } + } + return nil +} + +func (x *ExecSandboxInput) GetResize() *ExecSandboxWindowResize { + if x != nil { + if x, ok := x.Payload.(*ExecSandboxInput_Resize); ok { + return x.Resize + } + } + return nil +} + +type isExecSandboxInput_Payload interface { + isExecSandboxInput_Payload() +} + +type ExecSandboxInput_Start struct { + // First message: exec request metadata. + Start *ExecSandboxRequest `protobuf:"bytes,1,opt,name=start,proto3,oneof"` +} + +type ExecSandboxInput_Stdin struct { + // Subsequent messages: raw stdin bytes. + Stdin []byte `protobuf:"bytes,2,opt,name=stdin,proto3,oneof"` +} + +type ExecSandboxInput_Resize struct { + // Terminal window size change. + Resize *ExecSandboxWindowResize `protobuf:"bytes,3,opt,name=resize,proto3,oneof"` +} + +func (*ExecSandboxInput_Start) isExecSandboxInput_Payload() {} + +func (*ExecSandboxInput_Stdin) isExecSandboxInput_Payload() {} + +func (*ExecSandboxInput_Resize) isExecSandboxInput_Payload() {} + +// Terminal window resize event for interactive exec. +type ExecSandboxWindowResize struct { + state protoimpl.MessageState `protogen:"open.v1"` + Cols uint32 `protobuf:"varint,1,opt,name=cols,proto3" json:"cols,omitempty"` + Rows uint32 `protobuf:"varint,2,opt,name=rows,proto3" json:"rows,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecSandboxWindowResize) Reset() { + *x = ExecSandboxWindowResize{} + mi := &file_openshell_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecSandboxWindowResize) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecSandboxWindowResize) ProtoMessage() {} + +func (x *ExecSandboxWindowResize) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[53] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecSandboxWindowResize.ProtoReflect.Descriptor instead. +func (*ExecSandboxWindowResize) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{53} +} + +func (x *ExecSandboxWindowResize) GetCols() uint32 { + if x != nil { + return x.Cols + } + return 0 +} + +func (x *ExecSandboxWindowResize) GetRows() uint32 { + if x != nil { + return x.Rows + } + return 0 +} + +// SSH session record stored in persistence. +type SshSession struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Kubernetes-style metadata (id, name, labels, timestamps, resource version). + Metadata *datamodelv1.ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + // Sandbox id. + SandboxId string `protobuf:"bytes,2,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Session token. + Token string `protobuf:"bytes,3,opt,name=token,proto3" json:"token,omitempty"` + // Expiry timestamp in milliseconds since epoch. 0 means no expiry + // (backward-compatible default for sessions created before this field existed). + ExpiresAtMs int64 `protobuf:"varint,4,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + // Revoked flag. + Revoked bool `protobuf:"varint,5,opt,name=revoked,proto3" json:"revoked,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SshSession) Reset() { + *x = SshSession{} + mi := &file_openshell_proto_msgTypes[54] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SshSession) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SshSession) ProtoMessage() {} + +func (x *SshSession) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[54] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SshSession.ProtoReflect.Descriptor instead. +func (*SshSession) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{54} +} + +func (x *SshSession) GetMetadata() *datamodelv1.ObjectMeta { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *SshSession) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *SshSession) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *SshSession) GetExpiresAtMs() int64 { + if x != nil { + return x.ExpiresAtMs + } + return 0 +} + +func (x *SshSession) GetRevoked() bool { + if x != nil { + return x.Revoked + } + return false +} + +// Watch sandbox request. +type WatchSandboxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox id. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Stream sandbox status snapshots. + FollowStatus bool `protobuf:"varint,2,opt,name=follow_status,json=followStatus,proto3" json:"follow_status,omitempty"` + // Stream openshell-server process logs correlated to this sandbox. + FollowLogs bool `protobuf:"varint,3,opt,name=follow_logs,json=followLogs,proto3" json:"follow_logs,omitempty"` + // Stream platform events correlated to this sandbox. + FollowEvents bool `protobuf:"varint,4,opt,name=follow_events,json=followEvents,proto3" json:"follow_events,omitempty"` + // Replay the last N log lines (best-effort) before following. + LogTailLines uint32 `protobuf:"varint,5,opt,name=log_tail_lines,json=logTailLines,proto3" json:"log_tail_lines,omitempty"` + // Replay the last N platform events (best-effort) before following. + EventTail uint32 `protobuf:"varint,6,opt,name=event_tail,json=eventTail,proto3" json:"event_tail,omitempty"` + // Stop streaming once the sandbox reaches a terminal phase (READY or ERROR). + StopOnTerminal bool `protobuf:"varint,7,opt,name=stop_on_terminal,json=stopOnTerminal,proto3" json:"stop_on_terminal,omitempty"` + // Only include log lines with timestamp >= this value (milliseconds since epoch). + // 0 means no time filter. Applies to both tail replay and live streaming. + LogSinceMs int64 `protobuf:"varint,8,opt,name=log_since_ms,json=logSinceMs,proto3" json:"log_since_ms,omitempty"` + // Filter by log source (e.g. "gateway", "sandbox"). Empty means all sources. + LogSources []string `protobuf:"bytes,9,rep,name=log_sources,json=logSources,proto3" json:"log_sources,omitempty"` + // Minimum log level to include (e.g. "INFO", "WARN", "ERROR"). Empty means all levels. + LogMinLevel string `protobuf:"bytes,10,opt,name=log_min_level,json=logMinLevel,proto3" json:"log_min_level,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WatchSandboxRequest) Reset() { + *x = WatchSandboxRequest{} + mi := &file_openshell_proto_msgTypes[55] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WatchSandboxRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchSandboxRequest) ProtoMessage() {} + +func (x *WatchSandboxRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[55] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchSandboxRequest.ProtoReflect.Descriptor instead. +func (*WatchSandboxRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{55} +} + +func (x *WatchSandboxRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *WatchSandboxRequest) GetFollowStatus() bool { + if x != nil { + return x.FollowStatus + } + return false +} + +func (x *WatchSandboxRequest) GetFollowLogs() bool { + if x != nil { + return x.FollowLogs + } + return false +} + +func (x *WatchSandboxRequest) GetFollowEvents() bool { + if x != nil { + return x.FollowEvents + } + return false +} + +func (x *WatchSandboxRequest) GetLogTailLines() uint32 { + if x != nil { + return x.LogTailLines + } + return 0 +} + +func (x *WatchSandboxRequest) GetEventTail() uint32 { + if x != nil { + return x.EventTail + } + return 0 +} + +func (x *WatchSandboxRequest) GetStopOnTerminal() bool { + if x != nil { + return x.StopOnTerminal + } + return false +} + +func (x *WatchSandboxRequest) GetLogSinceMs() int64 { + if x != nil { + return x.LogSinceMs + } + return 0 +} + +func (x *WatchSandboxRequest) GetLogSources() []string { + if x != nil { + return x.LogSources + } + return nil +} + +func (x *WatchSandboxRequest) GetLogMinLevel() string { + if x != nil { + return x.LogMinLevel + } + return "" +} + +// One event in a sandbox watch stream. +type SandboxStreamEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *SandboxStreamEvent_Sandbox + // *SandboxStreamEvent_Log + // *SandboxStreamEvent_Event + // *SandboxStreamEvent_Warning + // *SandboxStreamEvent_DraftPolicyUpdate + Payload isSandboxStreamEvent_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxStreamEvent) Reset() { + *x = SandboxStreamEvent{} + mi := &file_openshell_proto_msgTypes[56] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxStreamEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxStreamEvent) ProtoMessage() {} + +func (x *SandboxStreamEvent) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[56] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxStreamEvent.ProtoReflect.Descriptor instead. +func (*SandboxStreamEvent) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{56} +} + +func (x *SandboxStreamEvent) GetPayload() isSandboxStreamEvent_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *SandboxStreamEvent) GetSandbox() *Sandbox { + if x != nil { + if x, ok := x.Payload.(*SandboxStreamEvent_Sandbox); ok { + return x.Sandbox + } + } + return nil +} + +func (x *SandboxStreamEvent) GetLog() *SandboxLogLine { + if x != nil { + if x, ok := x.Payload.(*SandboxStreamEvent_Log); ok { + return x.Log + } + } + return nil +} + +func (x *SandboxStreamEvent) GetEvent() *PlatformEvent { + if x != nil { + if x, ok := x.Payload.(*SandboxStreamEvent_Event); ok { + return x.Event + } + } + return nil +} + +func (x *SandboxStreamEvent) GetWarning() *SandboxStreamWarning { + if x != nil { + if x, ok := x.Payload.(*SandboxStreamEvent_Warning); ok { + return x.Warning + } + } + return nil +} + +func (x *SandboxStreamEvent) GetDraftPolicyUpdate() *DraftPolicyUpdate { + if x != nil { + if x, ok := x.Payload.(*SandboxStreamEvent_DraftPolicyUpdate); ok { + return x.DraftPolicyUpdate + } + } + return nil +} + +type isSandboxStreamEvent_Payload interface { + isSandboxStreamEvent_Payload() +} + +type SandboxStreamEvent_Sandbox struct { + // Latest sandbox snapshot. + Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3,oneof"` +} + +type SandboxStreamEvent_Log struct { + // One server log line/event. + Log *SandboxLogLine `protobuf:"bytes,2,opt,name=log,proto3,oneof"` +} + +type SandboxStreamEvent_Event struct { + // One platform event. + Event *PlatformEvent `protobuf:"bytes,3,opt,name=event,proto3,oneof"` +} + +type SandboxStreamEvent_Warning struct { + // Warning from the server (e.g. missed messages due to lag). + Warning *SandboxStreamWarning `protobuf:"bytes,4,opt,name=warning,proto3,oneof"` +} + +type SandboxStreamEvent_DraftPolicyUpdate struct { + // Draft policy update notification. + DraftPolicyUpdate *DraftPolicyUpdate `protobuf:"bytes,5,opt,name=draft_policy_update,json=draftPolicyUpdate,proto3,oneof"` +} + +func (*SandboxStreamEvent_Sandbox) isSandboxStreamEvent_Payload() {} + +func (*SandboxStreamEvent_Log) isSandboxStreamEvent_Payload() {} + +func (*SandboxStreamEvent_Event) isSandboxStreamEvent_Payload() {} + +func (*SandboxStreamEvent_Warning) isSandboxStreamEvent_Payload() {} + +func (*SandboxStreamEvent_DraftPolicyUpdate) isSandboxStreamEvent_Payload() {} + +// Log line correlated to a sandbox. +type SandboxLogLine struct { + state protoimpl.MessageState `protogen:"open.v1"` + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + TimestampMs int64 `protobuf:"varint,2,opt,name=timestamp_ms,json=timestampMs,proto3" json:"timestamp_ms,omitempty"` + Level string `protobuf:"bytes,3,opt,name=level,proto3" json:"level,omitempty"` + Target string `protobuf:"bytes,4,opt,name=target,proto3" json:"target,omitempty"` + Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` + // Log source: "gateway" (server-side) or "sandbox" (supervisor). + // Empty is treated as "gateway" for backward compatibility. + Source string `protobuf:"bytes,6,opt,name=source,proto3" json:"source,omitempty"` + // Structured key-value fields from the tracing event (e.g. dst_host, action). + Fields map[string]string `protobuf:"bytes,7,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxLogLine) Reset() { + *x = SandboxLogLine{} + mi := &file_openshell_proto_msgTypes[57] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxLogLine) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxLogLine) ProtoMessage() {} + +func (x *SandboxLogLine) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[57] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxLogLine.ProtoReflect.Descriptor instead. +func (*SandboxLogLine) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{57} +} + +func (x *SandboxLogLine) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *SandboxLogLine) GetTimestampMs() int64 { + if x != nil { + return x.TimestampMs + } + return 0 +} + +func (x *SandboxLogLine) GetLevel() string { + if x != nil { + return x.Level + } + return "" +} + +func (x *SandboxLogLine) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *SandboxLogLine) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *SandboxLogLine) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *SandboxLogLine) GetFields() map[string]string { + if x != nil { + return x.Fields + } + return nil +} + +type SandboxStreamWarning struct { + state protoimpl.MessageState `protogen:"open.v1"` + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxStreamWarning) Reset() { + *x = SandboxStreamWarning{} + mi := &file_openshell_proto_msgTypes[58] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxStreamWarning) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxStreamWarning) ProtoMessage() {} + +func (x *SandboxStreamWarning) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[58] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxStreamWarning.ProtoReflect.Descriptor instead. +func (*SandboxStreamWarning) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{58} +} + +func (x *SandboxStreamWarning) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +// Create provider request. +type CreateProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Provider *datamodelv1.Provider `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + // Workspace for the provider. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateProviderRequest) Reset() { + *x = CreateProviderRequest{} + mi := &file_openshell_proto_msgTypes[59] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateProviderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateProviderRequest) ProtoMessage() {} + +func (x *CreateProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[59] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateProviderRequest.ProtoReflect.Descriptor instead. +func (*CreateProviderRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{59} +} + +func (x *CreateProviderRequest) GetProvider() *datamodelv1.Provider { + if x != nil { + return x.Provider + } + return nil +} + +func (x *CreateProviderRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Get provider request. +type GetProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetProviderRequest) Reset() { + *x = GetProviderRequest{} + mi := &file_openshell_proto_msgTypes[60] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetProviderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProviderRequest) ProtoMessage() {} + +func (x *GetProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[60] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProviderRequest.ProtoReflect.Descriptor instead. +func (*GetProviderRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{60} +} + +func (x *GetProviderRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetProviderRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// List providers request. +type ListProvidersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + // List across all workspaces. Mutually exclusive with workspace. + AllWorkspaces bool `protobuf:"varint,4,opt,name=all_workspaces,json=allWorkspaces,proto3" json:"all_workspaces,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListProvidersRequest) Reset() { + *x = ListProvidersRequest{} + mi := &file_openshell_proto_msgTypes[61] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListProvidersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProvidersRequest) ProtoMessage() {} + +func (x *ListProvidersRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[61] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListProvidersRequest.ProtoReflect.Descriptor instead. +func (*ListProvidersRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{61} +} + +func (x *ListProvidersRequest) GetLimit() uint32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListProvidersRequest) GetOffset() uint32 { + if x != nil { + return x.Offset + } + return 0 +} + +func (x *ListProvidersRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +func (x *ListProvidersRequest) GetAllWorkspaces() bool { + if x != nil { + return x.AllWorkspaces + } + return false +} + +// Update provider request. +type UpdateProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Provider *datamodelv1.Provider `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + // Optional per-credential expiry timestamps to merge into the provider. + // A zero value removes the expiry for that credential. + CredentialExpiresAtMs map[string]int64 `protobuf:"bytes,2,rep,name=credential_expires_at_ms,json=credentialExpiresAtMs,proto3" json:"credential_expires_at_ms,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateProviderRequest) Reset() { + *x = UpdateProviderRequest{} + mi := &file_openshell_proto_msgTypes[62] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateProviderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateProviderRequest) ProtoMessage() {} + +func (x *UpdateProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[62] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateProviderRequest.ProtoReflect.Descriptor instead. +func (*UpdateProviderRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{62} +} + +func (x *UpdateProviderRequest) GetProvider() *datamodelv1.Provider { + if x != nil { + return x.Provider + } + return nil +} + +func (x *UpdateProviderRequest) GetCredentialExpiresAtMs() map[string]int64 { + if x != nil { + return x.CredentialExpiresAtMs + } + return nil +} + +func (x *UpdateProviderRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Delete provider request. +type DeleteProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteProviderRequest) Reset() { + *x = DeleteProviderRequest{} + mi := &file_openshell_proto_msgTypes[63] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteProviderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteProviderRequest) ProtoMessage() {} + +func (x *DeleteProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[63] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteProviderRequest.ProtoReflect.Descriptor instead. +func (*DeleteProviderRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{63} +} + +func (x *DeleteProviderRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DeleteProviderRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Provider response. +type ProviderResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Provider *datamodelv1.Provider `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderResponse) Reset() { + *x = ProviderResponse{} + mi := &file_openshell_proto_msgTypes[64] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderResponse) ProtoMessage() {} + +func (x *ProviderResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[64] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderResponse.ProtoReflect.Descriptor instead. +func (*ProviderResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{64} +} + +func (x *ProviderResponse) GetProvider() *datamodelv1.Provider { + if x != nil { + return x.Provider + } + return nil +} + +// List providers response. +type ListProvidersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Providers []*datamodelv1.Provider `protobuf:"bytes,1,rep,name=providers,proto3" json:"providers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListProvidersResponse) Reset() { + *x = ListProvidersResponse{} + mi := &file_openshell_proto_msgTypes[65] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListProvidersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProvidersResponse) ProtoMessage() {} + +func (x *ListProvidersResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[65] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListProvidersResponse.ProtoReflect.Descriptor instead. +func (*ListProvidersResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{65} +} + +func (x *ListProvidersResponse) GetProviders() []*datamodelv1.Provider { + if x != nil { + return x.Providers + } + return nil +} + +// List provider type profiles request. +type ListProviderProfilesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + // Workspace scope. When set, returns workspace-scoped + built-in profiles. + // When empty, returns platform-scoped + built-in only. + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListProviderProfilesRequest) Reset() { + *x = ListProviderProfilesRequest{} + mi := &file_openshell_proto_msgTypes[66] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListProviderProfilesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProviderProfilesRequest) ProtoMessage() {} + +func (x *ListProviderProfilesRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[66] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListProviderProfilesRequest.ProtoReflect.Descriptor instead. +func (*ListProviderProfilesRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{66} +} + +func (x *ListProviderProfilesRequest) GetLimit() uint32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListProviderProfilesRequest) GetOffset() uint32 { + if x != nil { + return x.Offset + } + return 0 +} + +func (x *ListProviderProfilesRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Fetch provider type profile request. +type GetProviderProfileRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Workspace scope for two-tier profile resolution. When set, checks + // workspace-scoped profiles first, then platform-scoped, then built-in. + // When empty, checks platform-scoped then built-in only. + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetProviderProfileRequest) Reset() { + *x = GetProviderProfileRequest{} + mi := &file_openshell_proto_msgTypes[67] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetProviderProfileRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProviderProfileRequest) ProtoMessage() {} + +func (x *GetProviderProfileRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[67] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProviderProfileRequest.ProtoReflect.Descriptor instead. +func (*GetProviderProfileRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{67} +} + +func (x *GetProviderProfileRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *GetProviderProfileRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Provider profile payload with optional source metadata for diagnostics. +type ProviderProfileImportItem struct { + state protoimpl.MessageState `protogen:"open.v1"` + Profile *ProviderProfile `protobuf:"bytes,1,opt,name=profile,proto3" json:"profile,omitempty"` + Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderProfileImportItem) Reset() { + *x = ProviderProfileImportItem{} + mi := &file_openshell_proto_msgTypes[68] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderProfileImportItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderProfileImportItem) ProtoMessage() {} + +func (x *ProviderProfileImportItem) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[68] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderProfileImportItem.ProtoReflect.Descriptor instead. +func (*ProviderProfileImportItem) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{68} +} + +func (x *ProviderProfileImportItem) GetProfile() *ProviderProfile { + if x != nil { + return x.Profile + } + return nil +} + +func (x *ProviderProfileImportItem) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +// Provider profile validation diagnostic. +type ProviderProfileDiagnostic struct { + state protoimpl.MessageState `protogen:"open.v1"` + Source string `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` + ProfileId string `protobuf:"bytes,2,opt,name=profile_id,json=profileId,proto3" json:"profile_id,omitempty"` + Field string `protobuf:"bytes,3,opt,name=field,proto3" json:"field,omitempty"` + Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` + Severity string `protobuf:"bytes,5,opt,name=severity,proto3" json:"severity,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderProfileDiagnostic) Reset() { + *x = ProviderProfileDiagnostic{} + mi := &file_openshell_proto_msgTypes[69] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderProfileDiagnostic) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderProfileDiagnostic) ProtoMessage() {} + +func (x *ProviderProfileDiagnostic) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[69] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderProfileDiagnostic.ProtoReflect.Descriptor instead. +func (*ProviderProfileDiagnostic) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{69} +} + +func (x *ProviderProfileDiagnostic) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *ProviderProfileDiagnostic) GetProfileId() string { + if x != nil { + return x.ProfileId + } + return "" +} + +func (x *ProviderProfileDiagnostic) GetField() string { + if x != nil { + return x.Field + } + return "" +} + +func (x *ProviderProfileDiagnostic) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *ProviderProfileDiagnostic) GetSeverity() string { + if x != nil { + return x.Severity + } + return "" +} + +// Endpoint selector for token grant audience overrides. +type ProviderCredentialTokenGrantAudienceOverride struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Optional: endpoint host selector. If omitted, inherits the profile endpoint host. + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + // Optional: endpoint port selector. If omitted, matches the expanded profile endpoint port. + Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + // Optional: endpoint path selector. If omitted, inherits the profile endpoint path. + Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` + // Resource audience to request for matching endpoints. + Audience string `protobuf:"bytes,4,opt,name=audience,proto3" json:"audience,omitempty"` + // Optional: OAuth2 scopes to request. If omitted, inherits the token grant scopes. + Scopes []string `protobuf:"bytes,5,rep,name=scopes,proto3" json:"scopes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderCredentialTokenGrantAudienceOverride) Reset() { + *x = ProviderCredentialTokenGrantAudienceOverride{} + mi := &file_openshell_proto_msgTypes[70] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderCredentialTokenGrantAudienceOverride) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderCredentialTokenGrantAudienceOverride) ProtoMessage() {} + +func (x *ProviderCredentialTokenGrantAudienceOverride) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[70] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderCredentialTokenGrantAudienceOverride.ProtoReflect.Descriptor instead. +func (*ProviderCredentialTokenGrantAudienceOverride) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{70} +} + +func (x *ProviderCredentialTokenGrantAudienceOverride) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *ProviderCredentialTokenGrantAudienceOverride) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *ProviderCredentialTokenGrantAudienceOverride) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *ProviderCredentialTokenGrantAudienceOverride) GetAudience() string { + if x != nil { + return x.Audience + } + return "" +} + +func (x *ProviderCredentialTokenGrantAudienceOverride) GetScopes() []string { + if x != nil { + return x.Scopes + } + return nil +} + +// Provider credential token grant configuration. +// When present, the credential is obtained dynamically via OAuth2 grant when needed. +type ProviderCredentialTokenGrant struct { + state protoimpl.MessageState `protogen:"open.v1"` + // OAuth2 token endpoint URL (e.g., https://keycloak.example.com/realms/my-realm/protocol/openid-connect/token) + TokenEndpoint string `protobuf:"bytes,1,opt,name=token_endpoint,json=tokenEndpoint,proto3" json:"token_endpoint,omitempty"` + // Optional: default resource audience to request from the token service + Audience string `protobuf:"bytes,2,opt,name=audience,proto3" json:"audience,omitempty"` + // Optional: audience to request when fetching the JWT-SVID from SPIRE. + // If omitted, the sandbox derives this from token_endpoint. + JwtSvidAudience string `protobuf:"bytes,6,opt,name=jwt_svid_audience,json=jwtSvidAudience,proto3" json:"jwt_svid_audience,omitempty"` + // Optional: OAuth2 scopes to request + Scopes []string `protobuf:"bytes,3,rep,name=scopes,proto3" json:"scopes,omitempty"` + // Optional: override token cache TTL (seconds) + // If 0 or omitted, use expires_in from token response + CacheTtlSeconds int64 `protobuf:"varint,4,opt,name=cache_ttl_seconds,json=cacheTtlSeconds,proto3" json:"cache_ttl_seconds,omitempty"` + // Optional: endpoint-specific resource audience overrides. + AudienceOverrides []*ProviderCredentialTokenGrantAudienceOverride `protobuf:"bytes,5,rep,name=audience_overrides,json=audienceOverrides,proto3" json:"audience_overrides,omitempty"` + // Optional: OAuth2 client_assertion_type value. If omitted, OpenShell uses + // urn:ietf:params:oauth:client-assertion-type:jwt-bearer. + ClientAssertionType string `protobuf:"bytes,7,opt,name=client_assertion_type,json=clientAssertionType,proto3" json:"client_assertion_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderCredentialTokenGrant) Reset() { + *x = ProviderCredentialTokenGrant{} + mi := &file_openshell_proto_msgTypes[71] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderCredentialTokenGrant) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderCredentialTokenGrant) ProtoMessage() {} + +func (x *ProviderCredentialTokenGrant) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[71] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderCredentialTokenGrant.ProtoReflect.Descriptor instead. +func (*ProviderCredentialTokenGrant) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{71} +} + +func (x *ProviderCredentialTokenGrant) GetTokenEndpoint() string { + if x != nil { + return x.TokenEndpoint + } + return "" +} + +func (x *ProviderCredentialTokenGrant) GetAudience() string { + if x != nil { + return x.Audience + } + return "" +} + +func (x *ProviderCredentialTokenGrant) GetJwtSvidAudience() string { + if x != nil { + return x.JwtSvidAudience + } + return "" +} + +func (x *ProviderCredentialTokenGrant) GetScopes() []string { + if x != nil { + return x.Scopes + } + return nil +} + +func (x *ProviderCredentialTokenGrant) GetCacheTtlSeconds() int64 { + if x != nil { + return x.CacheTtlSeconds + } + return 0 +} + +func (x *ProviderCredentialTokenGrant) GetAudienceOverrides() []*ProviderCredentialTokenGrantAudienceOverride { + if x != nil { + return x.AudienceOverrides + } + return nil +} + +func (x *ProviderCredentialTokenGrant) GetClientAssertionType() string { + if x != nil { + return x.ClientAssertionType + } + return "" +} + +// Provider credential declaration. +type ProviderProfileCredential struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + EnvVars []string `protobuf:"bytes,3,rep,name=env_vars,json=envVars,proto3" json:"env_vars,omitempty"` + Required bool `protobuf:"varint,4,opt,name=required,proto3" json:"required,omitempty"` + AuthStyle string `protobuf:"bytes,5,opt,name=auth_style,json=authStyle,proto3" json:"auth_style,omitempty"` + HeaderName string `protobuf:"bytes,6,opt,name=header_name,json=headerName,proto3" json:"header_name,omitempty"` + QueryParam string `protobuf:"bytes,7,opt,name=query_param,json=queryParam,proto3" json:"query_param,omitempty"` + Refresh *ProviderCredentialRefresh `protobuf:"bytes,8,opt,name=refresh,proto3" json:"refresh,omitempty"` + PathTemplate string `protobuf:"bytes,9,opt,name=path_template,json=pathTemplate,proto3" json:"path_template,omitempty"` + TokenGrant *ProviderCredentialTokenGrant `protobuf:"bytes,10,opt,name=token_grant,json=tokenGrant,proto3" json:"token_grant,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderProfileCredential) Reset() { + *x = ProviderProfileCredential{} + mi := &file_openshell_proto_msgTypes[72] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderProfileCredential) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderProfileCredential) ProtoMessage() {} + +func (x *ProviderProfileCredential) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[72] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderProfileCredential.ProtoReflect.Descriptor instead. +func (*ProviderProfileCredential) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{72} +} + +func (x *ProviderProfileCredential) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ProviderProfileCredential) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *ProviderProfileCredential) GetEnvVars() []string { + if x != nil { + return x.EnvVars + } + return nil +} + +func (x *ProviderProfileCredential) GetRequired() bool { + if x != nil { + return x.Required + } + return false +} + +func (x *ProviderProfileCredential) GetAuthStyle() string { + if x != nil { + return x.AuthStyle + } + return "" +} + +func (x *ProviderProfileCredential) GetHeaderName() string { + if x != nil { + return x.HeaderName + } + return "" +} + +func (x *ProviderProfileCredential) GetQueryParam() string { + if x != nil { + return x.QueryParam + } + return "" +} + +func (x *ProviderProfileCredential) GetRefresh() *ProviderCredentialRefresh { + if x != nil { + return x.Refresh + } + return nil +} + +func (x *ProviderProfileCredential) GetPathTemplate() string { + if x != nil { + return x.PathTemplate + } + return "" +} + +func (x *ProviderProfileCredential) GetTokenGrant() *ProviderCredentialTokenGrant { + if x != nil { + return x.TokenGrant + } + return nil +} + +type ProviderCredentialRefreshMaterial struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + Required bool `protobuf:"varint,3,opt,name=required,proto3" json:"required,omitempty"` + Secret bool `protobuf:"varint,4,opt,name=secret,proto3" json:"secret,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderCredentialRefreshMaterial) Reset() { + *x = ProviderCredentialRefreshMaterial{} + mi := &file_openshell_proto_msgTypes[73] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderCredentialRefreshMaterial) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderCredentialRefreshMaterial) ProtoMessage() {} + +func (x *ProviderCredentialRefreshMaterial) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[73] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderCredentialRefreshMaterial.ProtoReflect.Descriptor instead. +func (*ProviderCredentialRefreshMaterial) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{73} +} + +func (x *ProviderCredentialRefreshMaterial) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ProviderCredentialRefreshMaterial) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *ProviderCredentialRefreshMaterial) GetRequired() bool { + if x != nil { + return x.Required + } + return false +} + +func (x *ProviderCredentialRefreshMaterial) GetSecret() bool { + if x != nil { + return x.Secret + } + return false +} + +// 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. +type ProviderCredentialRefreshOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Output string `protobuf:"bytes,1,opt,name=output,proto3" json:"output,omitempty"` // strategy-defined semantic output id (e.g. "session_token") + Credential string `protobuf:"bytes,2,opt,name=credential,proto3" json:"credential,omitempty"` // sibling credential name whose env_vars receive this output + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderCredentialRefreshOutput) Reset() { + *x = ProviderCredentialRefreshOutput{} + mi := &file_openshell_proto_msgTypes[74] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderCredentialRefreshOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderCredentialRefreshOutput) ProtoMessage() {} + +func (x *ProviderCredentialRefreshOutput) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[74] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderCredentialRefreshOutput.ProtoReflect.Descriptor instead. +func (*ProviderCredentialRefreshOutput) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{74} +} + +func (x *ProviderCredentialRefreshOutput) GetOutput() string { + if x != nil { + return x.Output + } + return "" +} + +func (x *ProviderCredentialRefreshOutput) GetCredential() string { + if x != nil { + return x.Credential + } + return "" +} + +type ProviderCredentialRefresh struct { + state protoimpl.MessageState `protogen:"open.v1"` + Strategy ProviderCredentialRefreshStrategy `protobuf:"varint,1,opt,name=strategy,proto3,enum=openshell.v1.ProviderCredentialRefreshStrategy" json:"strategy,omitempty"` + TokenUrl string `protobuf:"bytes,2,opt,name=token_url,json=tokenUrl,proto3" json:"token_url,omitempty"` + Scopes []string `protobuf:"bytes,3,rep,name=scopes,proto3" json:"scopes,omitempty"` + RefreshBeforeSeconds int64 `protobuf:"varint,4,opt,name=refresh_before_seconds,json=refreshBeforeSeconds,proto3" json:"refresh_before_seconds,omitempty"` + MaxLifetimeSeconds int64 `protobuf:"varint,5,opt,name=max_lifetime_seconds,json=maxLifetimeSeconds,proto3" json:"max_lifetime_seconds,omitempty"` + Material []*ProviderCredentialRefreshMaterial `protobuf:"bytes,6,rep,name=material,proto3" json:"material,omitempty"` + AdditionalOutputs []*ProviderCredentialRefreshOutput `protobuf:"bytes,7,rep,name=additional_outputs,json=additionalOutputs,proto3" json:"additional_outputs,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderCredentialRefresh) Reset() { + *x = ProviderCredentialRefresh{} + mi := &file_openshell_proto_msgTypes[75] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderCredentialRefresh) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderCredentialRefresh) ProtoMessage() {} + +func (x *ProviderCredentialRefresh) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[75] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderCredentialRefresh.ProtoReflect.Descriptor instead. +func (*ProviderCredentialRefresh) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{75} +} + +func (x *ProviderCredentialRefresh) GetStrategy() ProviderCredentialRefreshStrategy { + if x != nil { + return x.Strategy + } + return ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED +} + +func (x *ProviderCredentialRefresh) GetTokenUrl() string { + if x != nil { + return x.TokenUrl + } + return "" +} + +func (x *ProviderCredentialRefresh) GetScopes() []string { + if x != nil { + return x.Scopes + } + return nil +} + +func (x *ProviderCredentialRefresh) GetRefreshBeforeSeconds() int64 { + if x != nil { + return x.RefreshBeforeSeconds + } + return 0 +} + +func (x *ProviderCredentialRefresh) GetMaxLifetimeSeconds() int64 { + if x != nil { + return x.MaxLifetimeSeconds + } + return 0 +} + +func (x *ProviderCredentialRefresh) GetMaterial() []*ProviderCredentialRefreshMaterial { + if x != nil { + return x.Material + } + return nil +} + +func (x *ProviderCredentialRefresh) GetAdditionalOutputs() []*ProviderCredentialRefreshOutput { + if x != nil { + return x.AdditionalOutputs + } + return nil +} + +type ProviderCredentialRefreshStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + ProviderName string `protobuf:"bytes,1,opt,name=provider_name,json=providerName,proto3" json:"provider_name,omitempty"` + ProviderId string `protobuf:"bytes,2,opt,name=provider_id,json=providerId,proto3" json:"provider_id,omitempty"` + CredentialKey string `protobuf:"bytes,3,opt,name=credential_key,json=credentialKey,proto3" json:"credential_key,omitempty"` + Strategy ProviderCredentialRefreshStrategy `protobuf:"varint,4,opt,name=strategy,proto3,enum=openshell.v1.ProviderCredentialRefreshStrategy" json:"strategy,omitempty"` + Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` + ExpiresAtMs int64 `protobuf:"varint,6,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + NextRefreshAtMs int64 `protobuf:"varint,7,opt,name=next_refresh_at_ms,json=nextRefreshAtMs,proto3" json:"next_refresh_at_ms,omitempty"` + LastRefreshAtMs int64 `protobuf:"varint,8,opt,name=last_refresh_at_ms,json=lastRefreshAtMs,proto3" json:"last_refresh_at_ms,omitempty"` + LastError string `protobuf:"bytes,9,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderCredentialRefreshStatus) Reset() { + *x = ProviderCredentialRefreshStatus{} + mi := &file_openshell_proto_msgTypes[76] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderCredentialRefreshStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderCredentialRefreshStatus) ProtoMessage() {} + +func (x *ProviderCredentialRefreshStatus) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[76] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderCredentialRefreshStatus.ProtoReflect.Descriptor instead. +func (*ProviderCredentialRefreshStatus) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{76} +} + +func (x *ProviderCredentialRefreshStatus) GetProviderName() string { + if x != nil { + return x.ProviderName + } + return "" +} + +func (x *ProviderCredentialRefreshStatus) GetProviderId() string { + if x != nil { + return x.ProviderId + } + return "" +} + +func (x *ProviderCredentialRefreshStatus) GetCredentialKey() string { + if x != nil { + return x.CredentialKey + } + return "" +} + +func (x *ProviderCredentialRefreshStatus) GetStrategy() ProviderCredentialRefreshStrategy { + if x != nil { + return x.Strategy + } + return ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED +} + +func (x *ProviderCredentialRefreshStatus) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ProviderCredentialRefreshStatus) GetExpiresAtMs() int64 { + if x != nil { + return x.ExpiresAtMs + } + return 0 +} + +func (x *ProviderCredentialRefreshStatus) GetNextRefreshAtMs() int64 { + if x != nil { + return x.NextRefreshAtMs + } + return 0 +} + +func (x *ProviderCredentialRefreshStatus) GetLastRefreshAtMs() int64 { + if x != nil { + return x.LastRefreshAtMs + } + return 0 +} + +func (x *ProviderCredentialRefreshStatus) GetLastError() string { + if x != nil { + return x.LastError + } + return "" +} + +// Provider profile local discovery declaration. +type ProviderProfileDiscovery struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Credential names from ProviderProfile.credentials eligible for local discovery. + Credentials []string `protobuf:"bytes,1,rep,name=credentials,proto3" json:"credentials,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderProfileDiscovery) Reset() { + *x = ProviderProfileDiscovery{} + mi := &file_openshell_proto_msgTypes[77] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderProfileDiscovery) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderProfileDiscovery) ProtoMessage() {} + +func (x *ProviderProfileDiscovery) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[77] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderProfileDiscovery.ProtoReflect.Descriptor instead. +func (*ProviderProfileDiscovery) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{77} +} + +func (x *ProviderProfileDiscovery) GetCredentials() []string { + if x != nil { + return x.Credentials + } + return nil +} + +type StoredProviderCredentialRefreshState struct { + state protoimpl.MessageState `protogen:"open.v1"` + Metadata *datamodelv1.ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + ProviderId string `protobuf:"bytes,2,opt,name=provider_id,json=providerId,proto3" json:"provider_id,omitempty"` + ProviderName string `protobuf:"bytes,3,opt,name=provider_name,json=providerName,proto3" json:"provider_name,omitempty"` + CredentialKey string `protobuf:"bytes,4,opt,name=credential_key,json=credentialKey,proto3" json:"credential_key,omitempty"` + Strategy ProviderCredentialRefreshStrategy `protobuf:"varint,5,opt,name=strategy,proto3,enum=openshell.v1.ProviderCredentialRefreshStrategy" json:"strategy,omitempty"` + Material map[string]string `protobuf:"bytes,6,rep,name=material,proto3" json:"material,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + SecretMaterialKeys []string `protobuf:"bytes,7,rep,name=secret_material_keys,json=secretMaterialKeys,proto3" json:"secret_material_keys,omitempty"` + ExpiresAtMs int64 `protobuf:"varint,8,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + NextRefreshAtMs int64 `protobuf:"varint,9,opt,name=next_refresh_at_ms,json=nextRefreshAtMs,proto3" json:"next_refresh_at_ms,omitempty"` + LastRefreshAtMs int64 `protobuf:"varint,10,opt,name=last_refresh_at_ms,json=lastRefreshAtMs,proto3" json:"last_refresh_at_ms,omitempty"` + Status string `protobuf:"bytes,11,opt,name=status,proto3" json:"status,omitempty"` + LastError string `protobuf:"bytes,12,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"` + TokenUrl string `protobuf:"bytes,13,opt,name=token_url,json=tokenUrl,proto3" json:"token_url,omitempty"` + Scopes []string `protobuf:"bytes,14,rep,name=scopes,proto3" json:"scopes,omitempty"` + RefreshBeforeSeconds int64 `protobuf:"varint,15,opt,name=refresh_before_seconds,json=refreshBeforeSeconds,proto3" json:"refresh_before_seconds,omitempty"` + MaxLifetimeSeconds int64 `protobuf:"varint,16,opt,name=max_lifetime_seconds,json=maxLifetimeSeconds,proto3" json:"max_lifetime_seconds,omitempty"` + // Resolved mapping of strategy-defined output id -> concrete env key, pinned + // at configure time from the profile's additional_outputs. Read by minting, + // collision reservation, and env-key surfacing so later profile edits cannot + // silently redirect writes. + AdditionalOutputKeys map[string]string `protobuf:"bytes,17,rep,name=additional_output_keys,json=additionalOutputKeys,proto3" json:"additional_output_keys,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StoredProviderCredentialRefreshState) Reset() { + *x = StoredProviderCredentialRefreshState{} + mi := &file_openshell_proto_msgTypes[78] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StoredProviderCredentialRefreshState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StoredProviderCredentialRefreshState) ProtoMessage() {} + +func (x *StoredProviderCredentialRefreshState) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[78] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StoredProviderCredentialRefreshState.ProtoReflect.Descriptor instead. +func (*StoredProviderCredentialRefreshState) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{78} +} + +func (x *StoredProviderCredentialRefreshState) GetMetadata() *datamodelv1.ObjectMeta { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *StoredProviderCredentialRefreshState) GetProviderId() string { + if x != nil { + return x.ProviderId + } + return "" +} + +func (x *StoredProviderCredentialRefreshState) GetProviderName() string { + if x != nil { + return x.ProviderName + } + return "" +} + +func (x *StoredProviderCredentialRefreshState) GetCredentialKey() string { + if x != nil { + return x.CredentialKey + } + return "" +} + +func (x *StoredProviderCredentialRefreshState) GetStrategy() ProviderCredentialRefreshStrategy { + if x != nil { + return x.Strategy + } + return ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED +} + +func (x *StoredProviderCredentialRefreshState) GetMaterial() map[string]string { + if x != nil { + return x.Material + } + return nil +} + +func (x *StoredProviderCredentialRefreshState) GetSecretMaterialKeys() []string { + if x != nil { + return x.SecretMaterialKeys + } + return nil +} + +func (x *StoredProviderCredentialRefreshState) GetExpiresAtMs() int64 { + if x != nil { + return x.ExpiresAtMs + } + return 0 +} + +func (x *StoredProviderCredentialRefreshState) GetNextRefreshAtMs() int64 { + if x != nil { + return x.NextRefreshAtMs + } + return 0 +} + +func (x *StoredProviderCredentialRefreshState) GetLastRefreshAtMs() int64 { + if x != nil { + return x.LastRefreshAtMs + } + return 0 +} + +func (x *StoredProviderCredentialRefreshState) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *StoredProviderCredentialRefreshState) GetLastError() string { + if x != nil { + return x.LastError + } + return "" +} + +func (x *StoredProviderCredentialRefreshState) GetTokenUrl() string { + if x != nil { + return x.TokenUrl + } + return "" +} + +func (x *StoredProviderCredentialRefreshState) GetScopes() []string { + if x != nil { + return x.Scopes + } + return nil +} + +func (x *StoredProviderCredentialRefreshState) GetRefreshBeforeSeconds() int64 { + if x != nil { + return x.RefreshBeforeSeconds + } + return 0 +} + +func (x *StoredProviderCredentialRefreshState) GetMaxLifetimeSeconds() int64 { + if x != nil { + return x.MaxLifetimeSeconds + } + return 0 +} + +func (x *StoredProviderCredentialRefreshState) GetAdditionalOutputKeys() map[string]string { + if x != nil { + return x.AdditionalOutputKeys + } + return nil +} + +type GetProviderRefreshStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Provider string `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + CredentialKey string `protobuf:"bytes,2,opt,name=credential_key,json=credentialKey,proto3" json:"credential_key,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetProviderRefreshStatusRequest) Reset() { + *x = GetProviderRefreshStatusRequest{} + mi := &file_openshell_proto_msgTypes[79] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetProviderRefreshStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProviderRefreshStatusRequest) ProtoMessage() {} + +func (x *GetProviderRefreshStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[79] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProviderRefreshStatusRequest.ProtoReflect.Descriptor instead. +func (*GetProviderRefreshStatusRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{79} +} + +func (x *GetProviderRefreshStatusRequest) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *GetProviderRefreshStatusRequest) GetCredentialKey() string { + if x != nil { + return x.CredentialKey + } + return "" +} + +func (x *GetProviderRefreshStatusRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +type GetProviderRefreshStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Credentials []*ProviderCredentialRefreshStatus `protobuf:"bytes,1,rep,name=credentials,proto3" json:"credentials,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetProviderRefreshStatusResponse) Reset() { + *x = GetProviderRefreshStatusResponse{} + mi := &file_openshell_proto_msgTypes[80] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetProviderRefreshStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProviderRefreshStatusResponse) ProtoMessage() {} + +func (x *GetProviderRefreshStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[80] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProviderRefreshStatusResponse.ProtoReflect.Descriptor instead. +func (*GetProviderRefreshStatusResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{80} +} + +func (x *GetProviderRefreshStatusResponse) GetCredentials() []*ProviderCredentialRefreshStatus { + if x != nil { + return x.Credentials + } + return nil +} + +type ConfigureProviderRefreshRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Provider string `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + CredentialKey string `protobuf:"bytes,2,opt,name=credential_key,json=credentialKey,proto3" json:"credential_key,omitempty"` + Strategy ProviderCredentialRefreshStrategy `protobuf:"varint,3,opt,name=strategy,proto3,enum=openshell.v1.ProviderCredentialRefreshStrategy" json:"strategy,omitempty"` + Material map[string]string `protobuf:"bytes,4,rep,name=material,proto3" json:"material,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + SecretMaterialKeys []string `protobuf:"bytes,5,rep,name=secret_material_keys,json=secretMaterialKeys,proto3" json:"secret_material_keys,omitempty"` + ExpiresAtMs *int64 `protobuf:"varint,6,opt,name=expires_at_ms,json=expiresAtMs,proto3,oneof" json:"expires_at_ms,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,7,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfigureProviderRefreshRequest) Reset() { + *x = ConfigureProviderRefreshRequest{} + mi := &file_openshell_proto_msgTypes[81] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConfigureProviderRefreshRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigureProviderRefreshRequest) ProtoMessage() {} + +func (x *ConfigureProviderRefreshRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[81] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfigureProviderRefreshRequest.ProtoReflect.Descriptor instead. +func (*ConfigureProviderRefreshRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{81} +} + +func (x *ConfigureProviderRefreshRequest) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *ConfigureProviderRefreshRequest) GetCredentialKey() string { + if x != nil { + return x.CredentialKey + } + return "" +} + +func (x *ConfigureProviderRefreshRequest) GetStrategy() ProviderCredentialRefreshStrategy { + if x != nil { + return x.Strategy + } + return ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED +} + +func (x *ConfigureProviderRefreshRequest) GetMaterial() map[string]string { + if x != nil { + return x.Material + } + return nil +} + +func (x *ConfigureProviderRefreshRequest) GetSecretMaterialKeys() []string { + if x != nil { + return x.SecretMaterialKeys + } + return nil +} + +func (x *ConfigureProviderRefreshRequest) GetExpiresAtMs() int64 { + if x != nil && x.ExpiresAtMs != nil { + return *x.ExpiresAtMs + } + return 0 +} + +func (x *ConfigureProviderRefreshRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +type ConfigureProviderRefreshResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status *ProviderCredentialRefreshStatus `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfigureProviderRefreshResponse) Reset() { + *x = ConfigureProviderRefreshResponse{} + mi := &file_openshell_proto_msgTypes[82] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConfigureProviderRefreshResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigureProviderRefreshResponse) ProtoMessage() {} + +func (x *ConfigureProviderRefreshResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[82] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfigureProviderRefreshResponse.ProtoReflect.Descriptor instead. +func (*ConfigureProviderRefreshResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{82} +} + +func (x *ConfigureProviderRefreshResponse) GetStatus() *ProviderCredentialRefreshStatus { + if x != nil { + return x.Status + } + return nil +} + +type RotateProviderCredentialRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Provider string `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + CredentialKey string `protobuf:"bytes,2,opt,name=credential_key,json=credentialKey,proto3" json:"credential_key,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RotateProviderCredentialRequest) Reset() { + *x = RotateProviderCredentialRequest{} + mi := &file_openshell_proto_msgTypes[83] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RotateProviderCredentialRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RotateProviderCredentialRequest) ProtoMessage() {} + +func (x *RotateProviderCredentialRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[83] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RotateProviderCredentialRequest.ProtoReflect.Descriptor instead. +func (*RotateProviderCredentialRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{83} +} + +func (x *RotateProviderCredentialRequest) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *RotateProviderCredentialRequest) GetCredentialKey() string { + if x != nil { + return x.CredentialKey + } + return "" +} + +func (x *RotateProviderCredentialRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +type RotateProviderCredentialResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status *ProviderCredentialRefreshStatus `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RotateProviderCredentialResponse) Reset() { + *x = RotateProviderCredentialResponse{} + mi := &file_openshell_proto_msgTypes[84] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RotateProviderCredentialResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RotateProviderCredentialResponse) ProtoMessage() {} + +func (x *RotateProviderCredentialResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[84] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RotateProviderCredentialResponse.ProtoReflect.Descriptor instead. +func (*RotateProviderCredentialResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{84} +} + +func (x *RotateProviderCredentialResponse) GetStatus() *ProviderCredentialRefreshStatus { + if x != nil { + return x.Status + } + return nil +} + +type DeleteProviderRefreshRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Provider string `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + CredentialKey string `protobuf:"bytes,2,opt,name=credential_key,json=credentialKey,proto3" json:"credential_key,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteProviderRefreshRequest) Reset() { + *x = DeleteProviderRefreshRequest{} + mi := &file_openshell_proto_msgTypes[85] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteProviderRefreshRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteProviderRefreshRequest) ProtoMessage() {} + +func (x *DeleteProviderRefreshRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[85] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteProviderRefreshRequest.ProtoReflect.Descriptor instead. +func (*DeleteProviderRefreshRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{85} +} + +func (x *DeleteProviderRefreshRequest) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *DeleteProviderRefreshRequest) GetCredentialKey() string { + if x != nil { + return x.CredentialKey + } + return "" +} + +func (x *DeleteProviderRefreshRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +type DeleteProviderRefreshResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteProviderRefreshResponse) Reset() { + *x = DeleteProviderRefreshResponse{} + mi := &file_openshell_proto_msgTypes[86] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteProviderRefreshResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteProviderRefreshResponse) ProtoMessage() {} + +func (x *DeleteProviderRefreshResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[86] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteProviderRefreshResponse.ProtoReflect.Descriptor instead. +func (*DeleteProviderRefreshResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{86} +} + +func (x *DeleteProviderRefreshResponse) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + +// Provider type profile metadata exposed to clients. +type ProviderProfile struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + Category ProviderProfileCategory `protobuf:"varint,4,opt,name=category,proto3,enum=openshell.v1.ProviderProfileCategory" json:"category,omitempty"` + Credentials []*ProviderProfileCredential `protobuf:"bytes,5,rep,name=credentials,proto3" json:"credentials,omitempty"` + Endpoints []*sandboxv1.NetworkEndpoint `protobuf:"bytes,6,rep,name=endpoints,proto3" json:"endpoints,omitempty"` + Binaries []*sandboxv1.NetworkBinary `protobuf:"bytes,7,rep,name=binaries,proto3" json:"binaries,omitempty"` + InferenceCapable bool `protobuf:"varint,8,opt,name=inference_capable,json=inferenceCapable,proto3" json:"inference_capable,omitempty"` + Discovery *ProviderProfileDiscovery `protobuf:"bytes,9,opt,name=discovery,proto3" json:"discovery,omitempty"` + // 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. + ResourceVersion uint64 `protobuf:"varint,10,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // Optional non-secret annotations attached by profile sources or importers. + Annotations map[string]string `protobuf:"bytes,11,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Server-set provenance: "builtin", "user", or "interceptor/{name}". + // Ignored on import/update payloads. + Source string `protobuf:"bytes,12,opt,name=source,proto3" json:"source,omitempty"` + // Server-set visibility: "platform", "workspace", or empty for + // non-scoped sources. Ignored on import/update payloads. + Scope string `protobuf:"bytes,13,opt,name=scope,proto3" json:"scope,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderProfile) Reset() { + *x = ProviderProfile{} + mi := &file_openshell_proto_msgTypes[87] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderProfile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderProfile) ProtoMessage() {} + +func (x *ProviderProfile) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[87] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderProfile.ProtoReflect.Descriptor instead. +func (*ProviderProfile) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{87} +} + +func (x *ProviderProfile) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ProviderProfile) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *ProviderProfile) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *ProviderProfile) GetCategory() ProviderProfileCategory { + if x != nil { + return x.Category + } + return ProviderProfileCategory_PROVIDER_PROFILE_CATEGORY_UNSPECIFIED +} + +func (x *ProviderProfile) GetCredentials() []*ProviderProfileCredential { + if x != nil { + return x.Credentials + } + return nil +} + +func (x *ProviderProfile) GetEndpoints() []*sandboxv1.NetworkEndpoint { + if x != nil { + return x.Endpoints + } + return nil +} + +func (x *ProviderProfile) GetBinaries() []*sandboxv1.NetworkBinary { + if x != nil { + return x.Binaries + } + return nil +} + +func (x *ProviderProfile) GetInferenceCapable() bool { + if x != nil { + return x.InferenceCapable + } + return false +} + +func (x *ProviderProfile) GetDiscovery() *ProviderProfileDiscovery { + if x != nil { + return x.Discovery + } + return nil +} + +func (x *ProviderProfile) GetResourceVersion() uint64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *ProviderProfile) GetAnnotations() map[string]string { + if x != nil { + return x.Annotations + } + return nil +} + +func (x *ProviderProfile) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *ProviderProfile) GetScope() string { + if x != nil { + return x.Scope + } + return "" +} + +// Stored custom provider profile object. +type StoredProviderProfile struct { + state protoimpl.MessageState `protogen:"open.v1"` + Metadata *datamodelv1.ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + Profile *ProviderProfile `protobuf:"bytes,2,opt,name=profile,proto3" json:"profile,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StoredProviderProfile) Reset() { + *x = StoredProviderProfile{} + mi := &file_openshell_proto_msgTypes[88] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StoredProviderProfile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StoredProviderProfile) ProtoMessage() {} + +func (x *StoredProviderProfile) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[88] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StoredProviderProfile.ProtoReflect.Descriptor instead. +func (*StoredProviderProfile) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{88} +} + +func (x *StoredProviderProfile) GetMetadata() *datamodelv1.ObjectMeta { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *StoredProviderProfile) GetProfile() *ProviderProfile { + if x != nil { + return x.Profile + } + return nil +} + +// Provider profile response. +type ProviderProfileResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Profile *ProviderProfile `protobuf:"bytes,1,opt,name=profile,proto3" json:"profile,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderProfileResponse) Reset() { + *x = ProviderProfileResponse{} + mi := &file_openshell_proto_msgTypes[89] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderProfileResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderProfileResponse) ProtoMessage() {} + +func (x *ProviderProfileResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[89] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderProfileResponse.ProtoReflect.Descriptor instead. +func (*ProviderProfileResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{89} +} + +func (x *ProviderProfileResponse) GetProfile() *ProviderProfile { + if x != nil { + return x.Profile + } + return nil +} + +// List provider profiles response. +type ListProviderProfilesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Profiles []*ProviderProfile `protobuf:"bytes,1,rep,name=profiles,proto3" json:"profiles,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListProviderProfilesResponse) Reset() { + *x = ListProviderProfilesResponse{} + mi := &file_openshell_proto_msgTypes[90] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListProviderProfilesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProviderProfilesResponse) ProtoMessage() {} + +func (x *ListProviderProfilesResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[90] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListProviderProfilesResponse.ProtoReflect.Descriptor instead. +func (*ListProviderProfilesResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{90} +} + +func (x *ListProviderProfilesResponse) GetProfiles() []*ProviderProfile { + if x != nil { + return x.Profiles + } + return nil +} + +// Import custom provider profiles request. +type ImportProviderProfilesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Profiles []*ProviderProfileImportItem `protobuf:"bytes,1,rep,name=profiles,proto3" json:"profiles,omitempty"` + // Workspace scope. When set, profiles are workspace-scoped (Workspace Admin). + // When empty, profiles are platform-scoped (Platform Admin). + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ImportProviderProfilesRequest) Reset() { + *x = ImportProviderProfilesRequest{} + mi := &file_openshell_proto_msgTypes[91] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ImportProviderProfilesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImportProviderProfilesRequest) ProtoMessage() {} + +func (x *ImportProviderProfilesRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[91] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImportProviderProfilesRequest.ProtoReflect.Descriptor instead. +func (*ImportProviderProfilesRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{91} +} + +func (x *ImportProviderProfilesRequest) GetProfiles() []*ProviderProfileImportItem { + if x != nil { + return x.Profiles + } + return nil +} + +func (x *ImportProviderProfilesRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Import custom provider profiles response. +type ImportProviderProfilesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Diagnostics []*ProviderProfileDiagnostic `protobuf:"bytes,1,rep,name=diagnostics,proto3" json:"diagnostics,omitempty"` + Profiles []*ProviderProfile `protobuf:"bytes,2,rep,name=profiles,proto3" json:"profiles,omitempty"` + Imported bool `protobuf:"varint,3,opt,name=imported,proto3" json:"imported,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ImportProviderProfilesResponse) Reset() { + *x = ImportProviderProfilesResponse{} + mi := &file_openshell_proto_msgTypes[92] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ImportProviderProfilesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImportProviderProfilesResponse) ProtoMessage() {} + +func (x *ImportProviderProfilesResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[92] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImportProviderProfilesResponse.ProtoReflect.Descriptor instead. +func (*ImportProviderProfilesResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{92} +} + +func (x *ImportProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { + if x != nil { + return x.Diagnostics + } + return nil +} + +func (x *ImportProviderProfilesResponse) GetProfiles() []*ProviderProfile { + if x != nil { + return x.Profiles + } + return nil +} + +func (x *ImportProviderProfilesResponse) GetImported() bool { + if x != nil { + return x.Imported + } + return false +} + +// Update one custom provider profile request. +type UpdateProviderProfilesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Profile *ProviderProfileImportItem `protobuf:"bytes,1,opt,name=profile,proto3" json:"profile,omitempty"` + // 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. + ExpectedResourceVersion uint64 `protobuf:"varint,2,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` + // Existing custom provider profile ID to update. The payload ID must match. + Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"` + // Workspace scope. When set, targets workspace-scoped profile. When empty, + // targets platform-scoped profile. + Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateProviderProfilesRequest) Reset() { + *x = UpdateProviderProfilesRequest{} + mi := &file_openshell_proto_msgTypes[93] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateProviderProfilesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateProviderProfilesRequest) ProtoMessage() {} + +func (x *UpdateProviderProfilesRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[93] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateProviderProfilesRequest.ProtoReflect.Descriptor instead. +func (*UpdateProviderProfilesRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{93} +} + +func (x *UpdateProviderProfilesRequest) GetProfile() *ProviderProfileImportItem { + if x != nil { + return x.Profile + } + return nil +} + +func (x *UpdateProviderProfilesRequest) GetExpectedResourceVersion() uint64 { + if x != nil { + return x.ExpectedResourceVersion + } + return 0 +} + +func (x *UpdateProviderProfilesRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *UpdateProviderProfilesRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Update one custom provider profile response. +type UpdateProviderProfilesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Diagnostics []*ProviderProfileDiagnostic `protobuf:"bytes,1,rep,name=diagnostics,proto3" json:"diagnostics,omitempty"` + Profile *ProviderProfile `protobuf:"bytes,2,opt,name=profile,proto3" json:"profile,omitempty"` + Updated bool `protobuf:"varint,3,opt,name=updated,proto3" json:"updated,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateProviderProfilesResponse) Reset() { + *x = UpdateProviderProfilesResponse{} + mi := &file_openshell_proto_msgTypes[94] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateProviderProfilesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateProviderProfilesResponse) ProtoMessage() {} + +func (x *UpdateProviderProfilesResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[94] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateProviderProfilesResponse.ProtoReflect.Descriptor instead. +func (*UpdateProviderProfilesResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{94} +} + +func (x *UpdateProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { + if x != nil { + return x.Diagnostics + } + return nil +} + +func (x *UpdateProviderProfilesResponse) GetProfile() *ProviderProfile { + if x != nil { + return x.Profile + } + return nil +} + +func (x *UpdateProviderProfilesResponse) GetUpdated() bool { + if x != nil { + return x.Updated + } + return false +} + +// Lint provider profiles request. +type LintProviderProfilesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Profiles []*ProviderProfileImportItem `protobuf:"bytes,1,rep,name=profiles,proto3" json:"profiles,omitempty"` + // Workspace scope. Used to check for conflicts against existing profiles + // in the target workspace. + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LintProviderProfilesRequest) Reset() { + *x = LintProviderProfilesRequest{} + mi := &file_openshell_proto_msgTypes[95] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LintProviderProfilesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LintProviderProfilesRequest) ProtoMessage() {} + +func (x *LintProviderProfilesRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[95] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LintProviderProfilesRequest.ProtoReflect.Descriptor instead. +func (*LintProviderProfilesRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{95} +} + +func (x *LintProviderProfilesRequest) GetProfiles() []*ProviderProfileImportItem { + if x != nil { + return x.Profiles + } + return nil +} + +func (x *LintProviderProfilesRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Lint provider profiles response. +type LintProviderProfilesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Diagnostics []*ProviderProfileDiagnostic `protobuf:"bytes,1,rep,name=diagnostics,proto3" json:"diagnostics,omitempty"` + Valid bool `protobuf:"varint,2,opt,name=valid,proto3" json:"valid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LintProviderProfilesResponse) Reset() { + *x = LintProviderProfilesResponse{} + mi := &file_openshell_proto_msgTypes[96] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LintProviderProfilesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LintProviderProfilesResponse) ProtoMessage() {} + +func (x *LintProviderProfilesResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[96] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LintProviderProfilesResponse.ProtoReflect.Descriptor instead. +func (*LintProviderProfilesResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{96} +} + +func (x *LintProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { + if x != nil { + return x.Diagnostics + } + return nil +} + +func (x *LintProviderProfilesResponse) GetValid() bool { + if x != nil { + return x.Valid + } + return false +} + +// Delete provider response. +type DeleteProviderResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteProviderResponse) Reset() { + *x = DeleteProviderResponse{} + mi := &file_openshell_proto_msgTypes[97] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteProviderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteProviderResponse) ProtoMessage() {} + +func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[97] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteProviderResponse.ProtoReflect.Descriptor instead. +func (*DeleteProviderResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{97} +} + +func (x *DeleteProviderResponse) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + +// Delete custom provider profile request. +type DeleteProviderProfileRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Workspace scope. When set, targets workspace-scoped profile. When empty, + // targets platform-scoped profile. + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteProviderProfileRequest) Reset() { + *x = DeleteProviderProfileRequest{} + mi := &file_openshell_proto_msgTypes[98] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteProviderProfileRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteProviderProfileRequest) ProtoMessage() {} + +func (x *DeleteProviderProfileRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[98] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteProviderProfileRequest.ProtoReflect.Descriptor instead. +func (*DeleteProviderProfileRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{98} +} + +func (x *DeleteProviderProfileRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *DeleteProviderProfileRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Delete custom provider profile response. +type DeleteProviderProfileResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteProviderProfileResponse) Reset() { + *x = DeleteProviderProfileResponse{} + mi := &file_openshell_proto_msgTypes[99] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteProviderProfileResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteProviderProfileResponse) ProtoMessage() {} + +func (x *DeleteProviderProfileResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[99] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteProviderProfileResponse.ProtoReflect.Descriptor instead. +func (*DeleteProviderProfileResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{99} +} + +func (x *DeleteProviderProfileResponse) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + +// Get sandbox provider environment request. +type GetSandboxProviderEnvironmentRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The sandbox ID. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSandboxProviderEnvironmentRequest) Reset() { + *x = GetSandboxProviderEnvironmentRequest{} + mi := &file_openshell_proto_msgTypes[100] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSandboxProviderEnvironmentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSandboxProviderEnvironmentRequest) ProtoMessage() {} + +func (x *GetSandboxProviderEnvironmentRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[100] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSandboxProviderEnvironmentRequest.ProtoReflect.Descriptor instead. +func (*GetSandboxProviderEnvironmentRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{100} +} + +func (x *GetSandboxProviderEnvironmentRequest) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +// Get sandbox provider environment response. +type GetSandboxProviderEnvironmentResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Provider credential environment variables. + Environment map[string]string `protobuf:"bytes,1,rep,name=environment,proto3" json:"environment,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Fingerprint for the provider credential inputs that produced environment. + ProviderEnvRevision uint64 `protobuf:"varint,2,opt,name=provider_env_revision,json=providerEnvRevision,proto3" json:"provider_env_revision,omitempty"` + // Expiration timestamps for returned environment variables. + CredentialExpiresAtMs map[string]int64 `protobuf:"bytes,3,rep,name=credential_expires_at_ms,json=credentialExpiresAtMs,proto3" json:"credential_expires_at_ms,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + // 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. + DynamicCredentials map[string]*ProviderProfileCredential `protobuf:"bytes,4,rep,name=dynamic_credentials,json=dynamicCredentials,proto3" json:"dynamic_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSandboxProviderEnvironmentResponse) Reset() { + *x = GetSandboxProviderEnvironmentResponse{} + mi := &file_openshell_proto_msgTypes[101] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSandboxProviderEnvironmentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSandboxProviderEnvironmentResponse) ProtoMessage() {} + +func (x *GetSandboxProviderEnvironmentResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[101] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSandboxProviderEnvironmentResponse.ProtoReflect.Descriptor instead. +func (*GetSandboxProviderEnvironmentResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{101} +} + +func (x *GetSandboxProviderEnvironmentResponse) GetEnvironment() map[string]string { + if x != nil { + return x.Environment + } + return nil +} + +func (x *GetSandboxProviderEnvironmentResponse) GetProviderEnvRevision() uint64 { + if x != nil { + return x.ProviderEnvRevision + } + return 0 +} + +func (x *GetSandboxProviderEnvironmentResponse) GetCredentialExpiresAtMs() map[string]int64 { + if x != nil { + return x.CredentialExpiresAtMs + } + return nil +} + +func (x *GetSandboxProviderEnvironmentResponse) GetDynamicCredentials() map[string]*ProviderProfileCredential { + if x != nil { + return x.DynamicCredentials + } + return nil +} + +// Update sandbox policy request. +type UpdateConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name (canonical lookup key). Required for sandbox-scoped updates. + // Not required when `global=true`. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The new policy to apply. + // + // Sandbox scope (`global=false`): + // - only network_policies and inference fields may differ from create-time + // policy; static fields must match version 1. + // + // Global scope (`global=true`): + // - applies to all sandboxes in full (no merge). + Policy *sandboxv1.SandboxPolicy `protobuf:"bytes,2,opt,name=policy,proto3" json:"policy,omitempty"` + // Optional single setting key to mutate. + SettingKey string `protobuf:"bytes,3,opt,name=setting_key,json=settingKey,proto3" json:"setting_key,omitempty"` + // Setting value for upsert operations. + SettingValue *sandboxv1.SettingValue `protobuf:"bytes,4,opt,name=setting_value,json=settingValue,proto3" json:"setting_value,omitempty"` + // Delete the setting key from scope. + // Sandbox-scoped deletes are rejected; only global delete is supported. + DeleteSetting bool `protobuf:"varint,5,opt,name=delete_setting,json=deleteSetting,proto3" json:"delete_setting,omitempty"` + // Apply mutation at gateway-global scope. + Global bool `protobuf:"varint,6,opt,name=global,proto3" json:"global,omitempty"` + // Batched incremental policy merge operations. Sandbox-scoped only. + MergeOperations []*PolicyMergeOperation `protobuf:"bytes,7,rep,name=merge_operations,json=mergeOperations,proto3" json:"merge_operations,omitempty"` + // 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. + ExpectedResourceVersion uint64 `protobuf:"varint,8,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` + // 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. + Annotations map[string]string `protobuf:"bytes,9,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Workspace scope. Empty defaults to "default". Ignored for global-scoped updates. + Workspace string `protobuf:"bytes,10,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateConfigRequest) Reset() { + *x = UpdateConfigRequest{} + mi := &file_openshell_proto_msgTypes[102] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateConfigRequest) ProtoMessage() {} + +func (x *UpdateConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[102] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateConfigRequest.ProtoReflect.Descriptor instead. +func (*UpdateConfigRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{102} +} + +func (x *UpdateConfigRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UpdateConfigRequest) GetPolicy() *sandboxv1.SandboxPolicy { + if x != nil { + return x.Policy + } + return nil +} + +func (x *UpdateConfigRequest) GetSettingKey() string { + if x != nil { + return x.SettingKey + } + return "" +} + +func (x *UpdateConfigRequest) GetSettingValue() *sandboxv1.SettingValue { + if x != nil { + return x.SettingValue + } + return nil +} + +func (x *UpdateConfigRequest) GetDeleteSetting() bool { + if x != nil { + return x.DeleteSetting + } + return false +} + +func (x *UpdateConfigRequest) GetGlobal() bool { + if x != nil { + return x.Global + } + return false +} + +func (x *UpdateConfigRequest) GetMergeOperations() []*PolicyMergeOperation { + if x != nil { + return x.MergeOperations + } + return nil +} + +func (x *UpdateConfigRequest) GetExpectedResourceVersion() uint64 { + if x != nil { + return x.ExpectedResourceVersion + } + return 0 +} + +func (x *UpdateConfigRequest) GetAnnotations() map[string]string { + if x != nil { + return x.Annotations + } + return nil +} + +func (x *UpdateConfigRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +type PolicyMergeOperation struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Operation: + // + // *PolicyMergeOperation_AddRule + // *PolicyMergeOperation_RemoveEndpoint + // *PolicyMergeOperation_RemoveRule + // *PolicyMergeOperation_AddDenyRules + // *PolicyMergeOperation_AddAllowRules + // *PolicyMergeOperation_RemoveBinary + Operation isPolicyMergeOperation_Operation `protobuf_oneof:"operation"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyMergeOperation) Reset() { + *x = PolicyMergeOperation{} + mi := &file_openshell_proto_msgTypes[103] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyMergeOperation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyMergeOperation) ProtoMessage() {} + +func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[103] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyMergeOperation.ProtoReflect.Descriptor instead. +func (*PolicyMergeOperation) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{103} +} + +func (x *PolicyMergeOperation) GetOperation() isPolicyMergeOperation_Operation { + if x != nil { + return x.Operation + } + return nil +} + +func (x *PolicyMergeOperation) GetAddRule() *AddNetworkRule { + if x != nil { + if x, ok := x.Operation.(*PolicyMergeOperation_AddRule); ok { + return x.AddRule + } + } + return nil +} + +func (x *PolicyMergeOperation) GetRemoveEndpoint() *RemoveNetworkEndpoint { + if x != nil { + if x, ok := x.Operation.(*PolicyMergeOperation_RemoveEndpoint); ok { + return x.RemoveEndpoint + } + } + return nil +} + +func (x *PolicyMergeOperation) GetRemoveRule() *RemoveNetworkRule { + if x != nil { + if x, ok := x.Operation.(*PolicyMergeOperation_RemoveRule); ok { + return x.RemoveRule + } + } + return nil +} + +func (x *PolicyMergeOperation) GetAddDenyRules() *AddDenyRules { + if x != nil { + if x, ok := x.Operation.(*PolicyMergeOperation_AddDenyRules); ok { + return x.AddDenyRules + } + } + return nil +} + +func (x *PolicyMergeOperation) GetAddAllowRules() *AddAllowRules { + if x != nil { + if x, ok := x.Operation.(*PolicyMergeOperation_AddAllowRules); ok { + return x.AddAllowRules + } + } + return nil +} + +func (x *PolicyMergeOperation) GetRemoveBinary() *RemoveNetworkBinary { + if x != nil { + if x, ok := x.Operation.(*PolicyMergeOperation_RemoveBinary); ok { + return x.RemoveBinary + } + } + return nil +} + +type isPolicyMergeOperation_Operation interface { + isPolicyMergeOperation_Operation() +} + +type PolicyMergeOperation_AddRule struct { + AddRule *AddNetworkRule `protobuf:"bytes,1,opt,name=add_rule,json=addRule,proto3,oneof"` +} + +type PolicyMergeOperation_RemoveEndpoint struct { + RemoveEndpoint *RemoveNetworkEndpoint `protobuf:"bytes,2,opt,name=remove_endpoint,json=removeEndpoint,proto3,oneof"` +} + +type PolicyMergeOperation_RemoveRule struct { + RemoveRule *RemoveNetworkRule `protobuf:"bytes,3,opt,name=remove_rule,json=removeRule,proto3,oneof"` +} + +type PolicyMergeOperation_AddDenyRules struct { + AddDenyRules *AddDenyRules `protobuf:"bytes,4,opt,name=add_deny_rules,json=addDenyRules,proto3,oneof"` +} + +type PolicyMergeOperation_AddAllowRules struct { + AddAllowRules *AddAllowRules `protobuf:"bytes,5,opt,name=add_allow_rules,json=addAllowRules,proto3,oneof"` +} + +type PolicyMergeOperation_RemoveBinary struct { + RemoveBinary *RemoveNetworkBinary `protobuf:"bytes,6,opt,name=remove_binary,json=removeBinary,proto3,oneof"` +} + +func (*PolicyMergeOperation_AddRule) isPolicyMergeOperation_Operation() {} + +func (*PolicyMergeOperation_RemoveEndpoint) isPolicyMergeOperation_Operation() {} + +func (*PolicyMergeOperation_RemoveRule) isPolicyMergeOperation_Operation() {} + +func (*PolicyMergeOperation_AddDenyRules) isPolicyMergeOperation_Operation() {} + +func (*PolicyMergeOperation_AddAllowRules) isPolicyMergeOperation_Operation() {} + +func (*PolicyMergeOperation_RemoveBinary) isPolicyMergeOperation_Operation() {} + +type AddNetworkRule struct { + state protoimpl.MessageState `protogen:"open.v1"` + RuleName string `protobuf:"bytes,1,opt,name=rule_name,json=ruleName,proto3" json:"rule_name,omitempty"` + Rule *sandboxv1.NetworkPolicyRule `protobuf:"bytes,2,opt,name=rule,proto3" json:"rule,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AddNetworkRule) Reset() { + *x = AddNetworkRule{} + mi := &file_openshell_proto_msgTypes[104] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddNetworkRule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddNetworkRule) ProtoMessage() {} + +func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[104] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddNetworkRule.ProtoReflect.Descriptor instead. +func (*AddNetworkRule) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{104} +} + +func (x *AddNetworkRule) GetRuleName() string { + if x != nil { + return x.RuleName + } + return "" +} + +func (x *AddNetworkRule) GetRule() *sandboxv1.NetworkPolicyRule { + if x != nil { + return x.Rule + } + return nil +} + +type RemoveNetworkEndpoint struct { + state protoimpl.MessageState `protogen:"open.v1"` + RuleName string `protobuf:"bytes,1,opt,name=rule_name,json=ruleName,proto3" json:"rule_name,omitempty"` + Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"` + Port uint32 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveNetworkEndpoint) Reset() { + *x = RemoveNetworkEndpoint{} + mi := &file_openshell_proto_msgTypes[105] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveNetworkEndpoint) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveNetworkEndpoint) ProtoMessage() {} + +func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[105] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveNetworkEndpoint.ProtoReflect.Descriptor instead. +func (*RemoveNetworkEndpoint) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{105} +} + +func (x *RemoveNetworkEndpoint) GetRuleName() string { + if x != nil { + return x.RuleName + } + return "" +} + +func (x *RemoveNetworkEndpoint) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *RemoveNetworkEndpoint) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +type RemoveNetworkRule struct { + state protoimpl.MessageState `protogen:"open.v1"` + RuleName string `protobuf:"bytes,1,opt,name=rule_name,json=ruleName,proto3" json:"rule_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveNetworkRule) Reset() { + *x = RemoveNetworkRule{} + mi := &file_openshell_proto_msgTypes[106] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveNetworkRule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveNetworkRule) ProtoMessage() {} + +func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[106] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveNetworkRule.ProtoReflect.Descriptor instead. +func (*RemoveNetworkRule) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{106} +} + +func (x *RemoveNetworkRule) GetRuleName() string { + if x != nil { + return x.RuleName + } + return "" +} + +type AddDenyRules struct { + state protoimpl.MessageState `protogen:"open.v1"` + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + DenyRules []*sandboxv1.L7DenyRule `protobuf:"bytes,3,rep,name=deny_rules,json=denyRules,proto3" json:"deny_rules,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AddDenyRules) Reset() { + *x = AddDenyRules{} + mi := &file_openshell_proto_msgTypes[107] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddDenyRules) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddDenyRules) ProtoMessage() {} + +func (x *AddDenyRules) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[107] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddDenyRules.ProtoReflect.Descriptor instead. +func (*AddDenyRules) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{107} +} + +func (x *AddDenyRules) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *AddDenyRules) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *AddDenyRules) GetDenyRules() []*sandboxv1.L7DenyRule { + if x != nil { + return x.DenyRules + } + return nil +} + +type AddAllowRules struct { + state protoimpl.MessageState `protogen:"open.v1"` + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + Rules []*sandboxv1.L7Rule `protobuf:"bytes,3,rep,name=rules,proto3" json:"rules,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AddAllowRules) Reset() { + *x = AddAllowRules{} + mi := &file_openshell_proto_msgTypes[108] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddAllowRules) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddAllowRules) ProtoMessage() {} + +func (x *AddAllowRules) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[108] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddAllowRules.ProtoReflect.Descriptor instead. +func (*AddAllowRules) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{108} +} + +func (x *AddAllowRules) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *AddAllowRules) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *AddAllowRules) GetRules() []*sandboxv1.L7Rule { + if x != nil { + return x.Rules + } + return nil +} + +type RemoveNetworkBinary struct { + state protoimpl.MessageState `protogen:"open.v1"` + RuleName string `protobuf:"bytes,1,opt,name=rule_name,json=ruleName,proto3" json:"rule_name,omitempty"` + BinaryPath string `protobuf:"bytes,2,opt,name=binary_path,json=binaryPath,proto3" json:"binary_path,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveNetworkBinary) Reset() { + *x = RemoveNetworkBinary{} + mi := &file_openshell_proto_msgTypes[109] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveNetworkBinary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveNetworkBinary) ProtoMessage() {} + +func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[109] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveNetworkBinary.ProtoReflect.Descriptor instead. +func (*RemoveNetworkBinary) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{109} +} + +func (x *RemoveNetworkBinary) GetRuleName() string { + if x != nil { + return x.RuleName + } + return "" +} + +func (x *RemoveNetworkBinary) GetBinaryPath() string { + if x != nil { + return x.BinaryPath + } + return "" +} + +// Update sandbox policy response. +type UpdateConfigResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Assigned policy version (monotonically increasing per sandbox). + Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + // SHA-256 hash of the serialized policy payload. + PolicyHash string `protobuf:"bytes,2,opt,name=policy_hash,json=policyHash,proto3" json:"policy_hash,omitempty"` + // Settings revision for the scope that was modified. + SettingsRevision uint64 `protobuf:"varint,3,opt,name=settings_revision,json=settingsRevision,proto3" json:"settings_revision,omitempty"` + // True when a setting delete operation removed an existing key. + Deleted bool `protobuf:"varint,4,opt,name=deleted,proto3" json:"deleted,omitempty"` + // Sandbox metadata annotations after the update. Empty for global updates. + Annotations map[string]string `protobuf:"bytes,5,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateConfigResponse) Reset() { + *x = UpdateConfigResponse{} + mi := &file_openshell_proto_msgTypes[110] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateConfigResponse) ProtoMessage() {} + +func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[110] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateConfigResponse.ProtoReflect.Descriptor instead. +func (*UpdateConfigResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{110} +} + +func (x *UpdateConfigResponse) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *UpdateConfigResponse) GetPolicyHash() string { + if x != nil { + return x.PolicyHash + } + return "" +} + +func (x *UpdateConfigResponse) GetSettingsRevision() uint64 { + if x != nil { + return x.SettingsRevision + } + return 0 +} + +func (x *UpdateConfigResponse) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + +func (x *UpdateConfigResponse) GetAnnotations() map[string]string { + if x != nil { + return x.Annotations + } + return nil +} + +// Get sandbox policy status request. +type GetSandboxPolicyStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name (canonical lookup key). Ignored when global is true. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The specific policy version to query. 0 means latest. + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` + // Query global policy revisions instead of a sandbox-scoped one. + Global bool `protobuf:"varint,3,opt,name=global,proto3" json:"global,omitempty"` + // Workspace scope. Empty defaults to "default". Ignored when global is true. + Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSandboxPolicyStatusRequest) Reset() { + *x = GetSandboxPolicyStatusRequest{} + mi := &file_openshell_proto_msgTypes[111] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSandboxPolicyStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSandboxPolicyStatusRequest) ProtoMessage() {} + +func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[111] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSandboxPolicyStatusRequest.ProtoReflect.Descriptor instead. +func (*GetSandboxPolicyStatusRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{111} +} + +func (x *GetSandboxPolicyStatusRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetSandboxPolicyStatusRequest) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *GetSandboxPolicyStatusRequest) GetGlobal() bool { + if x != nil { + return x.Global + } + return false +} + +func (x *GetSandboxPolicyStatusRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Get sandbox policy status response. +type GetSandboxPolicyStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The queried policy revision. + Revision *SandboxPolicyRevision `protobuf:"bytes,1,opt,name=revision,proto3" json:"revision,omitempty"` + // The currently active (loaded) policy version for this sandbox. + ActiveVersion uint32 `protobuf:"varint,2,opt,name=active_version,json=activeVersion,proto3" json:"active_version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSandboxPolicyStatusResponse) Reset() { + *x = GetSandboxPolicyStatusResponse{} + mi := &file_openshell_proto_msgTypes[112] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSandboxPolicyStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSandboxPolicyStatusResponse) ProtoMessage() {} + +func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[112] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSandboxPolicyStatusResponse.ProtoReflect.Descriptor instead. +func (*GetSandboxPolicyStatusResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{112} +} + +func (x *GetSandboxPolicyStatusResponse) GetRevision() *SandboxPolicyRevision { + if x != nil { + return x.Revision + } + return nil +} + +func (x *GetSandboxPolicyStatusResponse) GetActiveVersion() uint32 { + if x != nil { + return x.ActiveVersion + } + return 0 +} + +// List sandbox policies request. +type ListSandboxPoliciesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name (canonical lookup key). Ignored when global is true. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Limit uint32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` + // List global policy revisions instead of sandbox-scoped ones. + Global bool `protobuf:"varint,4,opt,name=global,proto3" json:"global,omitempty"` + // Workspace scope. Empty defaults to "default". Ignored when global is true. + Workspace string `protobuf:"bytes,5,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSandboxPoliciesRequest) Reset() { + *x = ListSandboxPoliciesRequest{} + mi := &file_openshell_proto_msgTypes[113] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSandboxPoliciesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSandboxPoliciesRequest) ProtoMessage() {} + +func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[113] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSandboxPoliciesRequest.ProtoReflect.Descriptor instead. +func (*ListSandboxPoliciesRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{113} +} + +func (x *ListSandboxPoliciesRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ListSandboxPoliciesRequest) GetLimit() uint32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListSandboxPoliciesRequest) GetOffset() uint32 { + if x != nil { + return x.Offset + } + return 0 +} + +func (x *ListSandboxPoliciesRequest) GetGlobal() bool { + if x != nil { + return x.Global + } + return false +} + +func (x *ListSandboxPoliciesRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// List sandbox policies response. +type ListSandboxPoliciesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Revisions []*SandboxPolicyRevision `protobuf:"bytes,1,rep,name=revisions,proto3" json:"revisions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSandboxPoliciesResponse) Reset() { + *x = ListSandboxPoliciesResponse{} + mi := &file_openshell_proto_msgTypes[114] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSandboxPoliciesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSandboxPoliciesResponse) ProtoMessage() {} + +func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[114] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSandboxPoliciesResponse.ProtoReflect.Descriptor instead. +func (*ListSandboxPoliciesResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{114} +} + +func (x *ListSandboxPoliciesResponse) GetRevisions() []*SandboxPolicyRevision { + if x != nil { + return x.Revisions + } + return nil +} + +// Report policy load status (called by sandbox runtime after reload attempt). +type ReportPolicyStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox id. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // The policy version that was attempted. + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` + // Load result status. + Status PolicyStatus `protobuf:"varint,3,opt,name=status,proto3,enum=openshell.v1.PolicyStatus" json:"status,omitempty"` + // Error message if status is FAILED. + LoadError string `protobuf:"bytes,4,opt,name=load_error,json=loadError,proto3" json:"load_error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReportPolicyStatusRequest) Reset() { + *x = ReportPolicyStatusRequest{} + mi := &file_openshell_proto_msgTypes[115] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReportPolicyStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReportPolicyStatusRequest) ProtoMessage() {} + +func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[115] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReportPolicyStatusRequest.ProtoReflect.Descriptor instead. +func (*ReportPolicyStatusRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{115} +} + +func (x *ReportPolicyStatusRequest) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *ReportPolicyStatusRequest) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *ReportPolicyStatusRequest) GetStatus() PolicyStatus { + if x != nil { + return x.Status + } + return PolicyStatus_POLICY_STATUS_UNSPECIFIED +} + +func (x *ReportPolicyStatusRequest) GetLoadError() string { + if x != nil { + return x.LoadError + } + return "" +} + +// Report policy status response. +type ReportPolicyStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReportPolicyStatusResponse) Reset() { + *x = ReportPolicyStatusResponse{} + mi := &file_openshell_proto_msgTypes[116] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReportPolicyStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReportPolicyStatusResponse) ProtoMessage() {} + +func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[116] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReportPolicyStatusResponse.ProtoReflect.Descriptor instead. +func (*ReportPolicyStatusResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{116} +} + +// A versioned policy revision with metadata. +type SandboxPolicyRevision struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Policy version (monotonically increasing per sandbox). + Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + // SHA-256 hash of the serialized policy payload. + PolicyHash string `protobuf:"bytes,2,opt,name=policy_hash,json=policyHash,proto3" json:"policy_hash,omitempty"` + // Load status of this revision. + Status PolicyStatus `protobuf:"varint,3,opt,name=status,proto3,enum=openshell.v1.PolicyStatus" json:"status,omitempty"` + // Error message if status is FAILED. + LoadError string `protobuf:"bytes,4,opt,name=load_error,json=loadError,proto3" json:"load_error,omitempty"` + // Milliseconds since epoch when this revision was created. + CreatedAtMs int64 `protobuf:"varint,5,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + // Milliseconds since epoch when this revision was loaded by the sandbox. + LoadedAtMs int64 `protobuf:"varint,6,opt,name=loaded_at_ms,json=loadedAtMs,proto3" json:"loaded_at_ms,omitempty"` + // The full policy (only populated when explicitly requested). + Policy *sandboxv1.SandboxPolicy `protobuf:"bytes,7,opt,name=policy,proto3" json:"policy,omitempty"` + // Immutable provenance supplied with this policy revision. + Provenance map[string]string `protobuf:"bytes,8,rep,name=provenance,proto3" json:"provenance,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxPolicyRevision) Reset() { + *x = SandboxPolicyRevision{} + mi := &file_openshell_proto_msgTypes[117] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxPolicyRevision) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxPolicyRevision) ProtoMessage() {} + +func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[117] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxPolicyRevision.ProtoReflect.Descriptor instead. +func (*SandboxPolicyRevision) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{117} +} + +func (x *SandboxPolicyRevision) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *SandboxPolicyRevision) GetPolicyHash() string { + if x != nil { + return x.PolicyHash + } + return "" +} + +func (x *SandboxPolicyRevision) GetStatus() PolicyStatus { + if x != nil { + return x.Status + } + return PolicyStatus_POLICY_STATUS_UNSPECIFIED +} + +func (x *SandboxPolicyRevision) GetLoadError() string { + if x != nil { + return x.LoadError + } + return "" +} + +func (x *SandboxPolicyRevision) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *SandboxPolicyRevision) GetLoadedAtMs() int64 { + if x != nil { + return x.LoadedAtMs + } + return 0 +} + +func (x *SandboxPolicyRevision) GetPolicy() *sandboxv1.SandboxPolicy { + if x != nil { + return x.Policy + } + return nil +} + +func (x *SandboxPolicyRevision) GetProvenance() map[string]string { + if x != nil { + return x.Provenance + } + return nil +} + +// Get sandbox logs request (one-shot fetch). +type GetSandboxLogsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox id. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Maximum number of log lines to return. 0 means use default (2000). + Lines uint32 `protobuf:"varint,2,opt,name=lines,proto3" json:"lines,omitempty"` + // Only include logs with timestamp >= this value (ms since epoch). 0 means no filter. + SinceMs int64 `protobuf:"varint,3,opt,name=since_ms,json=sinceMs,proto3" json:"since_ms,omitempty"` + // Filter by log source (e.g. "gateway", "sandbox"). Empty means all sources. + Sources []string `protobuf:"bytes,4,rep,name=sources,proto3" json:"sources,omitempty"` + // Minimum log level to include (e.g. "INFO", "WARN", "ERROR"). Empty means all levels. + MinLevel string `protobuf:"bytes,5,opt,name=min_level,json=minLevel,proto3" json:"min_level,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,6,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSandboxLogsRequest) Reset() { + *x = GetSandboxLogsRequest{} + mi := &file_openshell_proto_msgTypes[118] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSandboxLogsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSandboxLogsRequest) ProtoMessage() {} + +func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[118] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSandboxLogsRequest.ProtoReflect.Descriptor instead. +func (*GetSandboxLogsRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{118} +} + +func (x *GetSandboxLogsRequest) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *GetSandboxLogsRequest) GetLines() uint32 { + if x != nil { + return x.Lines + } + return 0 +} + +func (x *GetSandboxLogsRequest) GetSinceMs() int64 { + if x != nil { + return x.SinceMs + } + return 0 +} + +func (x *GetSandboxLogsRequest) GetSources() []string { + if x != nil { + return x.Sources + } + return nil +} + +func (x *GetSandboxLogsRequest) GetMinLevel() string { + if x != nil { + return x.MinLevel + } + return "" +} + +func (x *GetSandboxLogsRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Batch of log lines pushed from sandbox to server. +type PushSandboxLogsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The sandbox ID. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Log lines to ingest. + Logs []*SandboxLogLine `protobuf:"bytes,2,rep,name=logs,proto3" json:"logs,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PushSandboxLogsRequest) Reset() { + *x = PushSandboxLogsRequest{} + mi := &file_openshell_proto_msgTypes[119] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PushSandboxLogsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushSandboxLogsRequest) ProtoMessage() {} + +func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[119] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushSandboxLogsRequest.ProtoReflect.Descriptor instead. +func (*PushSandboxLogsRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{119} +} + +func (x *PushSandboxLogsRequest) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *PushSandboxLogsRequest) GetLogs() []*SandboxLogLine { + if x != nil { + return x.Logs + } + return nil +} + +// Push sandbox logs response. +type PushSandboxLogsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PushSandboxLogsResponse) Reset() { + *x = PushSandboxLogsResponse{} + mi := &file_openshell_proto_msgTypes[120] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PushSandboxLogsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushSandboxLogsResponse) ProtoMessage() {} + +func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[120] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushSandboxLogsResponse.ProtoReflect.Descriptor instead. +func (*PushSandboxLogsResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{120} +} + +// Get sandbox logs response. +type GetSandboxLogsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Log lines in chronological order. + Logs []*SandboxLogLine `protobuf:"bytes,1,rep,name=logs,proto3" json:"logs,omitempty"` + // Total number of lines in the server's buffer for this sandbox. + BufferTotal uint32 `protobuf:"varint,2,opt,name=buffer_total,json=bufferTotal,proto3" json:"buffer_total,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSandboxLogsResponse) Reset() { + *x = GetSandboxLogsResponse{} + mi := &file_openshell_proto_msgTypes[121] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSandboxLogsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSandboxLogsResponse) ProtoMessage() {} + +func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[121] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSandboxLogsResponse.ProtoReflect.Descriptor instead. +func (*GetSandboxLogsResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{121} +} + +func (x *GetSandboxLogsResponse) GetLogs() []*SandboxLogLine { + if x != nil { + return x.Logs + } + return nil +} + +func (x *GetSandboxLogsResponse) GetBufferTotal() uint32 { + if x != nil { + return x.BufferTotal + } + return 0 +} + +// Envelope for supervisor-to-gateway messages on the ConnectSupervisor stream. +type SupervisorMessage struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *SupervisorMessage_Hello + // *SupervisorMessage_Heartbeat + // *SupervisorMessage_RelayOpenResult + // *SupervisorMessage_RelayClose + Payload isSupervisorMessage_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SupervisorMessage) Reset() { + *x = SupervisorMessage{} + mi := &file_openshell_proto_msgTypes[122] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SupervisorMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SupervisorMessage) ProtoMessage() {} + +func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[122] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SupervisorMessage.ProtoReflect.Descriptor instead. +func (*SupervisorMessage) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{122} +} + +func (x *SupervisorMessage) GetPayload() isSupervisorMessage_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *SupervisorMessage) GetHello() *SupervisorHello { + if x != nil { + if x, ok := x.Payload.(*SupervisorMessage_Hello); ok { + return x.Hello + } + } + return nil +} + +func (x *SupervisorMessage) GetHeartbeat() *SupervisorHeartbeat { + if x != nil { + if x, ok := x.Payload.(*SupervisorMessage_Heartbeat); ok { + return x.Heartbeat + } + } + return nil +} + +func (x *SupervisorMessage) GetRelayOpenResult() *RelayOpenResult { + if x != nil { + if x, ok := x.Payload.(*SupervisorMessage_RelayOpenResult); ok { + return x.RelayOpenResult + } + } + return nil +} + +func (x *SupervisorMessage) GetRelayClose() *RelayClose { + if x != nil { + if x, ok := x.Payload.(*SupervisorMessage_RelayClose); ok { + return x.RelayClose + } + } + return nil +} + +type isSupervisorMessage_Payload interface { + isSupervisorMessage_Payload() +} + +type SupervisorMessage_Hello struct { + Hello *SupervisorHello `protobuf:"bytes,1,opt,name=hello,proto3,oneof"` +} + +type SupervisorMessage_Heartbeat struct { + Heartbeat *SupervisorHeartbeat `protobuf:"bytes,2,opt,name=heartbeat,proto3,oneof"` +} + +type SupervisorMessage_RelayOpenResult struct { + RelayOpenResult *RelayOpenResult `protobuf:"bytes,3,opt,name=relay_open_result,json=relayOpenResult,proto3,oneof"` +} + +type SupervisorMessage_RelayClose struct { + RelayClose *RelayClose `protobuf:"bytes,4,opt,name=relay_close,json=relayClose,proto3,oneof"` +} + +func (*SupervisorMessage_Hello) isSupervisorMessage_Payload() {} + +func (*SupervisorMessage_Heartbeat) isSupervisorMessage_Payload() {} + +func (*SupervisorMessage_RelayOpenResult) isSupervisorMessage_Payload() {} + +func (*SupervisorMessage_RelayClose) isSupervisorMessage_Payload() {} + +// Envelope for gateway-to-supervisor messages on the ConnectSupervisor stream. +type GatewayMessage struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *GatewayMessage_SessionAccepted + // *GatewayMessage_SessionRejected + // *GatewayMessage_Heartbeat + // *GatewayMessage_RelayOpen + // *GatewayMessage_RelayClose + Payload isGatewayMessage_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GatewayMessage) Reset() { + *x = GatewayMessage{} + mi := &file_openshell_proto_msgTypes[123] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GatewayMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GatewayMessage) ProtoMessage() {} + +func (x *GatewayMessage) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[123] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GatewayMessage.ProtoReflect.Descriptor instead. +func (*GatewayMessage) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{123} +} + +func (x *GatewayMessage) GetPayload() isGatewayMessage_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *GatewayMessage) GetSessionAccepted() *SessionAccepted { + if x != nil { + if x, ok := x.Payload.(*GatewayMessage_SessionAccepted); ok { + return x.SessionAccepted + } + } + return nil +} + +func (x *GatewayMessage) GetSessionRejected() *SessionRejected { + if x != nil { + if x, ok := x.Payload.(*GatewayMessage_SessionRejected); ok { + return x.SessionRejected + } + } + return nil +} + +func (x *GatewayMessage) GetHeartbeat() *GatewayHeartbeat { + if x != nil { + if x, ok := x.Payload.(*GatewayMessage_Heartbeat); ok { + return x.Heartbeat + } + } + return nil +} + +func (x *GatewayMessage) GetRelayOpen() *RelayOpen { + if x != nil { + if x, ok := x.Payload.(*GatewayMessage_RelayOpen); ok { + return x.RelayOpen + } + } + return nil +} + +func (x *GatewayMessage) GetRelayClose() *RelayClose { + if x != nil { + if x, ok := x.Payload.(*GatewayMessage_RelayClose); ok { + return x.RelayClose + } + } + return nil +} + +type isGatewayMessage_Payload interface { + isGatewayMessage_Payload() +} + +type GatewayMessage_SessionAccepted struct { + SessionAccepted *SessionAccepted `protobuf:"bytes,1,opt,name=session_accepted,json=sessionAccepted,proto3,oneof"` +} + +type GatewayMessage_SessionRejected struct { + SessionRejected *SessionRejected `protobuf:"bytes,2,opt,name=session_rejected,json=sessionRejected,proto3,oneof"` +} + +type GatewayMessage_Heartbeat struct { + Heartbeat *GatewayHeartbeat `protobuf:"bytes,3,opt,name=heartbeat,proto3,oneof"` +} + +type GatewayMessage_RelayOpen struct { + RelayOpen *RelayOpen `protobuf:"bytes,4,opt,name=relay_open,json=relayOpen,proto3,oneof"` +} + +type GatewayMessage_RelayClose struct { + RelayClose *RelayClose `protobuf:"bytes,5,opt,name=relay_close,json=relayClose,proto3,oneof"` +} + +func (*GatewayMessage_SessionAccepted) isGatewayMessage_Payload() {} + +func (*GatewayMessage_SessionRejected) isGatewayMessage_Payload() {} + +func (*GatewayMessage_Heartbeat) isGatewayMessage_Payload() {} + +func (*GatewayMessage_RelayOpen) isGatewayMessage_Payload() {} + +func (*GatewayMessage_RelayClose) isGatewayMessage_Payload() {} + +// Supervisor identifies itself and the sandbox it manages. +type SupervisorHello struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox ID this supervisor manages. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Supervisor instance ID (e.g. boot id or process epoch). + InstanceId string `protobuf:"bytes,2,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SupervisorHello) Reset() { + *x = SupervisorHello{} + mi := &file_openshell_proto_msgTypes[124] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SupervisorHello) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SupervisorHello) ProtoMessage() {} + +func (x *SupervisorHello) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[124] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SupervisorHello.ProtoReflect.Descriptor instead. +func (*SupervisorHello) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{124} +} + +func (x *SupervisorHello) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *SupervisorHello) GetInstanceId() string { + if x != nil { + return x.InstanceId + } + return "" +} + +// Gateway accepts the supervisor session. +type SessionAccepted struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Gateway-assigned session ID for this connection. + SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + // Recommended heartbeat interval in seconds. + HeartbeatIntervalSecs uint32 `protobuf:"varint,2,opt,name=heartbeat_interval_secs,json=heartbeatIntervalSecs,proto3" json:"heartbeat_interval_secs,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SessionAccepted) Reset() { + *x = SessionAccepted{} + mi := &file_openshell_proto_msgTypes[125] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SessionAccepted) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SessionAccepted) ProtoMessage() {} + +func (x *SessionAccepted) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[125] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SessionAccepted.ProtoReflect.Descriptor instead. +func (*SessionAccepted) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{125} +} + +func (x *SessionAccepted) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *SessionAccepted) GetHeartbeatIntervalSecs() uint32 { + if x != nil { + return x.HeartbeatIntervalSecs + } + return 0 +} + +// Gateway rejects the supervisor session. +type SessionRejected struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Human-readable rejection reason. + Reason string `protobuf:"bytes,1,opt,name=reason,proto3" json:"reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SessionRejected) Reset() { + *x = SessionRejected{} + mi := &file_openshell_proto_msgTypes[126] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SessionRejected) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SessionRejected) ProtoMessage() {} + +func (x *SessionRejected) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[126] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SessionRejected.ProtoReflect.Descriptor instead. +func (*SessionRejected) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{126} +} + +func (x *SessionRejected) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +// Supervisor heartbeat. +type SupervisorHeartbeat struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SupervisorHeartbeat) Reset() { + *x = SupervisorHeartbeat{} + mi := &file_openshell_proto_msgTypes[127] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SupervisorHeartbeat) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SupervisorHeartbeat) ProtoMessage() {} + +func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[127] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SupervisorHeartbeat.ProtoReflect.Descriptor instead. +func (*SupervisorHeartbeat) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{127} +} + +// Gateway heartbeat. +type GatewayHeartbeat struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GatewayHeartbeat) Reset() { + *x = GatewayHeartbeat{} + mi := &file_openshell_proto_msgTypes[128] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GatewayHeartbeat) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GatewayHeartbeat) ProtoMessage() {} + +func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[128] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GatewayHeartbeat.ProtoReflect.Descriptor instead. +func (*GatewayHeartbeat) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{128} +} + +// 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. +type RelayOpen struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Gateway-allocated channel identifier (UUID). + ChannelId string `protobuf:"bytes,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // Target the supervisor should dial inside the sandbox. + // If absent, supervisors treat the relay as SSH for compatibility. + // + // Types that are valid to be assigned to Target: + // + // *RelayOpen_Ssh + // *RelayOpen_Tcp + Target isRelayOpen_Target `protobuf_oneof:"target"` + // Optional service identifier for audit/correlation. + ServiceId string `protobuf:"bytes,5,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RelayOpen) Reset() { + *x = RelayOpen{} + mi := &file_openshell_proto_msgTypes[129] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RelayOpen) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RelayOpen) ProtoMessage() {} + +func (x *RelayOpen) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[129] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RelayOpen.ProtoReflect.Descriptor instead. +func (*RelayOpen) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{129} +} + +func (x *RelayOpen) GetChannelId() string { + if x != nil { + return x.ChannelId + } + return "" +} + +func (x *RelayOpen) GetTarget() isRelayOpen_Target { + if x != nil { + return x.Target + } + return nil +} + +func (x *RelayOpen) GetSsh() *SshRelayTarget { + if x != nil { + if x, ok := x.Target.(*RelayOpen_Ssh); ok { + return x.Ssh + } + } + return nil +} + +func (x *RelayOpen) GetTcp() *TcpRelayTarget { + if x != nil { + if x, ok := x.Target.(*RelayOpen_Tcp); ok { + return x.Tcp + } + } + return nil +} + +func (x *RelayOpen) GetServiceId() string { + if x != nil { + return x.ServiceId + } + return "" +} + +type isRelayOpen_Target interface { + isRelayOpen_Target() +} + +type RelayOpen_Ssh struct { + Ssh *SshRelayTarget `protobuf:"bytes,2,opt,name=ssh,proto3,oneof"` +} + +type RelayOpen_Tcp struct { + Tcp *TcpRelayTarget `protobuf:"bytes,3,opt,name=tcp,proto3,oneof"` +} + +func (*RelayOpen_Ssh) isRelayOpen_Target() {} + +func (*RelayOpen_Tcp) isRelayOpen_Target() {} + +// Built-in SSH relay target. +type SshRelayTarget struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SshRelayTarget) Reset() { + *x = SshRelayTarget{} + mi := &file_openshell_proto_msgTypes[130] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SshRelayTarget) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SshRelayTarget) ProtoMessage() {} + +func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[130] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SshRelayTarget.ProtoReflect.Descriptor instead. +func (*SshRelayTarget) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{130} +} + +// TCP target dialed by the supervisor from inside the sandbox. +type TcpRelayTarget struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Phase 1 accepts loopback only: 127.0.0.1, ::1, or localhost. + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + // Target port. Must fit in u16 and be non-zero. + Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TcpRelayTarget) Reset() { + *x = TcpRelayTarget{} + mi := &file_openshell_proto_msgTypes[131] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TcpRelayTarget) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TcpRelayTarget) ProtoMessage() {} + +func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[131] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TcpRelayTarget.ProtoReflect.Descriptor instead. +func (*TcpRelayTarget) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{131} +} + +func (x *TcpRelayTarget) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *TcpRelayTarget) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +// Initial RelayStream frame sent by the supervisor to claim a pending relay. +type RelayInit struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Gateway-allocated channel identifier (UUID). + ChannelId string `protobuf:"bytes,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RelayInit) Reset() { + *x = RelayInit{} + mi := &file_openshell_proto_msgTypes[132] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RelayInit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RelayInit) ProtoMessage() {} + +func (x *RelayInit) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[132] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RelayInit.ProtoReflect.Descriptor instead. +func (*RelayInit) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{132} +} + +func (x *RelayInit) GetChannelId() string { + if x != nil { + return x.ChannelId + } + return "" +} + +// 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`. +type RelayFrame struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *RelayFrame_Init + // *RelayFrame_Data + Payload isRelayFrame_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RelayFrame) Reset() { + *x = RelayFrame{} + mi := &file_openshell_proto_msgTypes[133] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RelayFrame) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RelayFrame) ProtoMessage() {} + +func (x *RelayFrame) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[133] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RelayFrame.ProtoReflect.Descriptor instead. +func (*RelayFrame) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{133} +} + +func (x *RelayFrame) GetPayload() isRelayFrame_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *RelayFrame) GetInit() *RelayInit { + if x != nil { + if x, ok := x.Payload.(*RelayFrame_Init); ok { + return x.Init + } + } + return nil +} + +func (x *RelayFrame) GetData() []byte { + if x != nil { + if x, ok := x.Payload.(*RelayFrame_Data); ok { + return x.Data + } + } + return nil +} + +type isRelayFrame_Payload interface { + isRelayFrame_Payload() +} + +type RelayFrame_Init struct { + Init *RelayInit `protobuf:"bytes,1,opt,name=init,proto3,oneof"` +} + +type RelayFrame_Data struct { + Data []byte `protobuf:"bytes,2,opt,name=data,proto3,oneof"` +} + +func (*RelayFrame_Init) isRelayFrame_Payload() {} + +func (*RelayFrame_Data) isRelayFrame_Payload() {} + +// Supervisor reports the result of a relay open request. +type RelayOpenResult struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Channel identifier from the RelayOpen request. + ChannelId string `protobuf:"bytes,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // True if the relay was successfully established. + Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"` + // Error message if success is false. + Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RelayOpenResult) Reset() { + *x = RelayOpenResult{} + mi := &file_openshell_proto_msgTypes[134] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RelayOpenResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RelayOpenResult) ProtoMessage() {} + +func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[134] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RelayOpenResult.ProtoReflect.Descriptor instead. +func (*RelayOpenResult) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{134} +} + +func (x *RelayOpenResult) GetChannelId() string { + if x != nil { + return x.ChannelId + } + return "" +} + +func (x *RelayOpenResult) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *RelayOpenResult) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +// Either side requests closure of a relay channel. +type RelayClose struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Channel identifier to close. + ChannelId string `protobuf:"bytes,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // Optional reason for closure. + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RelayClose) Reset() { + *x = RelayClose{} + mi := &file_openshell_proto_msgTypes[135] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RelayClose) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RelayClose) ProtoMessage() {} + +func (x *RelayClose) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[135] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RelayClose.ProtoReflect.Descriptor instead. +func (*RelayClose) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{135} +} + +func (x *RelayClose) GetChannelId() string { + if x != nil { + return x.ChannelId + } + return "" +} + +func (x *RelayClose) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +// Observed HTTP method+path pattern from L7 inspection. +type L7RequestSample struct { + state protoimpl.MessageState `protogen:"open.v1"` + // HTTP method: GET, POST, PUT, DELETE, etc. + Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` + // HTTP path: /v1/models, /repos/myorg/issues + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + // L7 decision: "audit" or "deny" (allowed requests not collected). + Decision string `protobuf:"bytes,3,opt,name=decision,proto3" json:"decision,omitempty"` + // Number of times this (method, path) was observed. + Count uint32 `protobuf:"varint,4,opt,name=count,proto3" json:"count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *L7RequestSample) Reset() { + *x = L7RequestSample{} + mi := &file_openshell_proto_msgTypes[136] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *L7RequestSample) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*L7RequestSample) ProtoMessage() {} + +func (x *L7RequestSample) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[136] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use L7RequestSample.ProtoReflect.Descriptor instead. +func (*L7RequestSample) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{136} +} + +func (x *L7RequestSample) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *L7RequestSample) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *L7RequestSample) GetDecision() string { + if x != nil { + return x.Decision + } + return "" +} + +func (x *L7RequestSample) GetCount() uint32 { + if x != nil { + return x.Count + } + return 0 +} + +// Structured denial summary from sandbox aggregator. +type DenialSummary struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox ID that produced this summary. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Denied destination host. + Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"` + // Denied destination port. + Port uint32 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"` + // Binary that attempted the connection. + Binary string `protobuf:"bytes,4,opt,name=binary,proto3" json:"binary,omitempty"` + // Process ancestor chain. + Ancestors []string `protobuf:"bytes,5,rep,name=ancestors,proto3" json:"ancestors,omitempty"` + // Denial reason from OPA evaluation. + DenyReason string `protobuf:"bytes,6,opt,name=deny_reason,json=denyReason,proto3" json:"deny_reason,omitempty"` + // First denial timestamp (ms since epoch). + FirstSeenMs int64 `protobuf:"varint,7,opt,name=first_seen_ms,json=firstSeenMs,proto3" json:"first_seen_ms,omitempty"` + // Most recent denial timestamp (ms since epoch). + LastSeenMs int64 `protobuf:"varint,8,opt,name=last_seen_ms,json=lastSeenMs,proto3" json:"last_seen_ms,omitempty"` + // Number of denials in the current window. + Count uint32 `protobuf:"varint,9,opt,name=count,proto3" json:"count,omitempty"` + // Events dropped during aggregator cooldown. + SuppressedCount uint32 `protobuf:"varint,10,opt,name=suppressed_count,json=suppressedCount,proto3" json:"suppressed_count,omitempty"` + // Cumulative lifetime count (never resets). + TotalCount uint32 `protobuf:"varint,11,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"` + // Distinct cmdline strings observed (sanitized of credentials). + SampleCmdlines []string `protobuf:"bytes,12,rep,name=sample_cmdlines,json=sampleCmdlines,proto3" json:"sample_cmdlines,omitempty"` + // SHA-256 of the binary for audit trail. + BinarySha256 string `protobuf:"bytes,13,opt,name=binary_sha256,json=binarySha256,proto3" json:"binary_sha256,omitempty"` + // True if emitted by stale-flush rather than threshold. + Persistent bool `protobuf:"varint,14,opt,name=persistent,proto3" json:"persistent,omitempty"` + // Denial category: "l4_deny", "l7_deny", "l7_audit", "ssrf". + DenialStage string `protobuf:"bytes,15,opt,name=denial_stage,json=denialStage,proto3" json:"denial_stage,omitempty"` + // Observed HTTP request patterns (from L7 inspection). + L7RequestSamples []*L7RequestSample `protobuf:"bytes,16,rep,name=l7_request_samples,json=l7RequestSamples,proto3" json:"l7_request_samples,omitempty"` + // True if L7 inspection was active during observation window. + L7InspectionActive bool `protobuf:"varint,17,opt,name=l7_inspection_active,json=l7InspectionActive,proto3" json:"l7_inspection_active,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DenialSummary) Reset() { + *x = DenialSummary{} + mi := &file_openshell_proto_msgTypes[137] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DenialSummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DenialSummary) ProtoMessage() {} + +func (x *DenialSummary) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[137] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DenialSummary.ProtoReflect.Descriptor instead. +func (*DenialSummary) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{137} +} + +func (x *DenialSummary) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *DenialSummary) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *DenialSummary) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *DenialSummary) GetBinary() string { + if x != nil { + return x.Binary + } + return "" +} + +func (x *DenialSummary) GetAncestors() []string { + if x != nil { + return x.Ancestors + } + return nil +} + +func (x *DenialSummary) GetDenyReason() string { + if x != nil { + return x.DenyReason + } + return "" +} + +func (x *DenialSummary) GetFirstSeenMs() int64 { + if x != nil { + return x.FirstSeenMs + } + return 0 +} + +func (x *DenialSummary) GetLastSeenMs() int64 { + if x != nil { + return x.LastSeenMs + } + return 0 +} + +func (x *DenialSummary) GetCount() uint32 { + if x != nil { + return x.Count + } + return 0 +} + +func (x *DenialSummary) GetSuppressedCount() uint32 { + if x != nil { + return x.SuppressedCount + } + return 0 +} + +func (x *DenialSummary) GetTotalCount() uint32 { + if x != nil { + return x.TotalCount + } + return 0 +} + +func (x *DenialSummary) GetSampleCmdlines() []string { + if x != nil { + return x.SampleCmdlines + } + return nil +} + +func (x *DenialSummary) GetBinarySha256() string { + if x != nil { + return x.BinarySha256 + } + return "" +} + +func (x *DenialSummary) GetPersistent() bool { + if x != nil { + return x.Persistent + } + return false +} + +func (x *DenialSummary) GetDenialStage() string { + if x != nil { + return x.DenialStage + } + return "" +} + +func (x *DenialSummary) GetL7RequestSamples() []*L7RequestSample { + if x != nil { + return x.L7RequestSamples + } + return nil +} + +func (x *DenialSummary) GetL7InspectionActive() bool { + if x != nil { + return x.L7InspectionActive + } + return false +} + +// Count of denied actions grouped only by sanitized telemetry category. +type DenialGroupCount struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sanitized denial category, e.g. "connect_policy", "l7_policy", "ssrf". + DenyGroup string `protobuf:"bytes,1,opt,name=deny_group,json=denyGroup,proto3" json:"deny_group,omitempty"` + // Number of denied actions in this category. + DeniedCount uint32 `protobuf:"varint,2,opt,name=denied_count,json=deniedCount,proto3" json:"denied_count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DenialGroupCount) Reset() { + *x = DenialGroupCount{} + mi := &file_openshell_proto_msgTypes[138] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DenialGroupCount) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DenialGroupCount) ProtoMessage() {} + +func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[138] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DenialGroupCount.ProtoReflect.Descriptor instead. +func (*DenialGroupCount) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{138} +} + +func (x *DenialGroupCount) GetDenyGroup() string { + if x != nil { + return x.DenyGroup + } + return "" +} + +func (x *DenialGroupCount) GetDeniedCount() uint32 { + if x != nil { + return x.DeniedCount + } + return 0 +} + +// Anonymous sandbox network activity counters. This intentionally excludes +// hosts, paths, binaries, raw deny reasons, sandbox IDs, and user content. +type NetworkActivitySummary struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Total observed network activities in the current window. + NetworkActivityCount uint32 `protobuf:"varint,1,opt,name=network_activity_count,json=networkActivityCount,proto3" json:"network_activity_count,omitempty"` + // Total denied actions in the current window. + DeniedActionCount uint32 `protobuf:"varint,2,opt,name=denied_action_count,json=deniedActionCount,proto3" json:"denied_action_count,omitempty"` + // Denied action counts grouped by sanitized category. + DenialsByGroup []*DenialGroupCount `protobuf:"bytes,3,rep,name=denials_by_group,json=denialsByGroup,proto3" json:"denials_by_group,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkActivitySummary) Reset() { + *x = NetworkActivitySummary{} + mi := &file_openshell_proto_msgTypes[139] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkActivitySummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkActivitySummary) ProtoMessage() {} + +func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[139] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkActivitySummary.ProtoReflect.Descriptor instead. +func (*NetworkActivitySummary) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{139} +} + +func (x *NetworkActivitySummary) GetNetworkActivityCount() uint32 { + if x != nil { + return x.NetworkActivityCount + } + return 0 +} + +func (x *NetworkActivitySummary) GetDeniedActionCount() uint32 { + if x != nil { + return x.DeniedActionCount + } + return 0 +} + +func (x *NetworkActivitySummary) GetDenialsByGroup() []*DenialGroupCount { + if x != nil { + return x.DenialsByGroup + } + return nil +} + +// A proposed policy rule with rationale and approval status. +type PolicyChunk struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Unique chunk identifier. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Approval status: "pending", "approved", "rejected". + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + // Proposed network_policies map key. + RuleName string `protobuf:"bytes,3,opt,name=rule_name,json=ruleName,proto3" json:"rule_name,omitempty"` + // The proposed network policy rule. + ProposedRule *sandboxv1.NetworkPolicyRule `protobuf:"bytes,4,opt,name=proposed_rule,json=proposedRule,proto3" json:"proposed_rule,omitempty"` + // Human-readable explanation of why this rule is proposed. + Rationale string `protobuf:"bytes,5,opt,name=rationale,proto3" json:"rationale,omitempty"` + // Security concerns flagged by analysis (empty if none). + SecurityNotes string `protobuf:"bytes,6,opt,name=security_notes,json=securityNotes,proto3" json:"security_notes,omitempty"` + // Analysis confidence (0.0-1.0). 0 for mechanistic mode. + Confidence float32 `protobuf:"fixed32,7,opt,name=confidence,proto3" json:"confidence,omitempty"` + // IDs of denial summaries that led to this chunk. + DenialSummaryIds []string `protobuf:"bytes,8,rep,name=denial_summary_ids,json=denialSummaryIds,proto3" json:"denial_summary_ids,omitempty"` + // Creation timestamp (ms since epoch). + CreatedAtMs int64 `protobuf:"varint,9,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + // When the user approved/rejected (ms since epoch). 0 if undecided. + DecidedAtMs int64 `protobuf:"varint,10,opt,name=decided_at_ms,json=decidedAtMs,proto3" json:"decided_at_ms,omitempty"` + // Recommendation stage: "initial" or "refined" (progressive L7 visibility). + Stage string `protobuf:"bytes,11,opt,name=stage,proto3" json:"stage,omitempty"` + // For stage="refined": the initial chunk this replaces. + SupersedesChunkId string `protobuf:"bytes,12,opt,name=supersedes_chunk_id,json=supersedesChunkId,proto3" json:"supersedes_chunk_id,omitempty"` + // How many times this endpoint has been seen across denial flush cycles. + HitCount int32 `protobuf:"varint,13,opt,name=hit_count,json=hitCount,proto3" json:"hit_count,omitempty"` + // First time this endpoint was proposed (ms since epoch). + FirstSeenMs int64 `protobuf:"varint,14,opt,name=first_seen_ms,json=firstSeenMs,proto3" json:"first_seen_ms,omitempty"` + // Most recent time this endpoint was re-proposed (ms since epoch). + LastSeenMs int64 `protobuf:"varint,15,opt,name=last_seen_ms,json=lastSeenMs,proto3" json:"last_seen_ms,omitempty"` + // Binary path that triggered the denial (denormalized for display convenience). + Binary string `protobuf:"bytes,16,opt,name=binary,proto3" json:"binary,omitempty"` + // 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. + ValidationResult string `protobuf:"bytes,17,opt,name=validation_result,json=validationResult,proto3" json:"validation_result,omitempty"` + // 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. + RejectionReason string `protobuf:"bytes,18,opt,name=rejection_reason,json=rejectionReason,proto3" json:"rejection_reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyChunk) Reset() { + *x = PolicyChunk{} + mi := &file_openshell_proto_msgTypes[140] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyChunk) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyChunk) ProtoMessage() {} + +func (x *PolicyChunk) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[140] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyChunk.ProtoReflect.Descriptor instead. +func (*PolicyChunk) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{140} +} + +func (x *PolicyChunk) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *PolicyChunk) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *PolicyChunk) GetRuleName() string { + if x != nil { + return x.RuleName + } + return "" +} + +func (x *PolicyChunk) GetProposedRule() *sandboxv1.NetworkPolicyRule { + if x != nil { + return x.ProposedRule + } + return nil +} + +func (x *PolicyChunk) GetRationale() string { + if x != nil { + return x.Rationale + } + return "" +} + +func (x *PolicyChunk) GetSecurityNotes() string { + if x != nil { + return x.SecurityNotes + } + return "" +} + +func (x *PolicyChunk) GetConfidence() float32 { + if x != nil { + return x.Confidence + } + return 0 +} + +func (x *PolicyChunk) GetDenialSummaryIds() []string { + if x != nil { + return x.DenialSummaryIds + } + return nil +} + +func (x *PolicyChunk) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *PolicyChunk) GetDecidedAtMs() int64 { + if x != nil { + return x.DecidedAtMs + } + return 0 +} + +func (x *PolicyChunk) GetStage() string { + if x != nil { + return x.Stage + } + return "" +} + +func (x *PolicyChunk) GetSupersedesChunkId() string { + if x != nil { + return x.SupersedesChunkId + } + return "" +} + +func (x *PolicyChunk) GetHitCount() int32 { + if x != nil { + return x.HitCount + } + return 0 +} + +func (x *PolicyChunk) GetFirstSeenMs() int64 { + if x != nil { + return x.FirstSeenMs + } + return 0 +} + +func (x *PolicyChunk) GetLastSeenMs() int64 { + if x != nil { + return x.LastSeenMs + } + return 0 +} + +func (x *PolicyChunk) GetBinary() string { + if x != nil { + return x.Binary + } + return "" +} + +func (x *PolicyChunk) GetValidationResult() string { + if x != nil { + return x.ValidationResult + } + return "" +} + +func (x *PolicyChunk) GetRejectionReason() string { + if x != nil { + return x.RejectionReason + } + return "" +} + +// Notification that the draft policy was updated. +type DraftPolicyUpdate struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Current draft version. + DraftVersion uint64 `protobuf:"varint,1,opt,name=draft_version,json=draftVersion,proto3" json:"draft_version,omitempty"` + // Number of new chunks added in this update. + NewChunks uint32 `protobuf:"varint,2,opt,name=new_chunks,json=newChunks,proto3" json:"new_chunks,omitempty"` + // Total pending chunks awaiting approval. + TotalPending uint32 `protobuf:"varint,3,opt,name=total_pending,json=totalPending,proto3" json:"total_pending,omitempty"` + // Brief description of what changed. + Summary string `protobuf:"bytes,4,opt,name=summary,proto3" json:"summary,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DraftPolicyUpdate) Reset() { + *x = DraftPolicyUpdate{} + mi := &file_openshell_proto_msgTypes[141] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DraftPolicyUpdate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DraftPolicyUpdate) ProtoMessage() {} + +func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[141] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DraftPolicyUpdate.ProtoReflect.Descriptor instead. +func (*DraftPolicyUpdate) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{141} +} + +func (x *DraftPolicyUpdate) GetDraftVersion() uint64 { + if x != nil { + return x.DraftVersion + } + return 0 +} + +func (x *DraftPolicyUpdate) GetNewChunks() uint32 { + if x != nil { + return x.NewChunks + } + return 0 +} + +func (x *DraftPolicyUpdate) GetTotalPending() uint32 { + if x != nil { + return x.TotalPending + } + return 0 +} + +func (x *DraftPolicyUpdate) GetSummary() string { + if x != nil { + return x.Summary + } + return "" +} + +// Submit analysis results from sandbox to gateway. +type SubmitPolicyAnalysisRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Aggregated denial summaries. + Summaries []*DenialSummary `protobuf:"bytes,1,rep,name=summaries,proto3" json:"summaries,omitempty"` + // Proposed policy chunks (validated by sandbox OPA engine). + ProposedChunks []*PolicyChunk `protobuf:"bytes,2,rep,name=proposed_chunks,json=proposedChunks,proto3" json:"proposed_chunks,omitempty"` + // 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. + AnalysisMode string `protobuf:"bytes,3,opt,name=analysis_mode,json=analysisMode,proto3" json:"analysis_mode,omitempty"` + // Sandbox name. + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + // Anonymous network activity counters. + NetworkActivitySummaries []*NetworkActivitySummary `protobuf:"bytes,5,rep,name=network_activity_summaries,json=networkActivitySummaries,proto3" json:"network_activity_summaries,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,6,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubmitPolicyAnalysisRequest) Reset() { + *x = SubmitPolicyAnalysisRequest{} + mi := &file_openshell_proto_msgTypes[142] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubmitPolicyAnalysisRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitPolicyAnalysisRequest) ProtoMessage() {} + +func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[142] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitPolicyAnalysisRequest.ProtoReflect.Descriptor instead. +func (*SubmitPolicyAnalysisRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{142} +} + +func (x *SubmitPolicyAnalysisRequest) GetSummaries() []*DenialSummary { + if x != nil { + return x.Summaries + } + return nil +} + +func (x *SubmitPolicyAnalysisRequest) GetProposedChunks() []*PolicyChunk { + if x != nil { + return x.ProposedChunks + } + return nil +} + +func (x *SubmitPolicyAnalysisRequest) GetAnalysisMode() string { + if x != nil { + return x.AnalysisMode + } + return "" +} + +func (x *SubmitPolicyAnalysisRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SubmitPolicyAnalysisRequest) GetNetworkActivitySummaries() []*NetworkActivitySummary { + if x != nil { + return x.NetworkActivitySummaries + } + return nil +} + +func (x *SubmitPolicyAnalysisRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +type SubmitPolicyAnalysisResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Number of chunks accepted by the gateway. + AcceptedChunks uint32 `protobuf:"varint,1,opt,name=accepted_chunks,json=acceptedChunks,proto3" json:"accepted_chunks,omitempty"` + // Number of chunks rejected by gateway validation. + RejectedChunks uint32 `protobuf:"varint,2,opt,name=rejected_chunks,json=rejectedChunks,proto3" json:"rejected_chunks,omitempty"` + // Reasons for each rejected chunk. + RejectionReasons []string `protobuf:"bytes,3,rep,name=rejection_reasons,json=rejectionReasons,proto3" json:"rejection_reasons,omitempty"` + // 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. + AcceptedChunkIds []string `protobuf:"bytes,4,rep,name=accepted_chunk_ids,json=acceptedChunkIds,proto3" json:"accepted_chunk_ids,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubmitPolicyAnalysisResponse) Reset() { + *x = SubmitPolicyAnalysisResponse{} + mi := &file_openshell_proto_msgTypes[143] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubmitPolicyAnalysisResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitPolicyAnalysisResponse) ProtoMessage() {} + +func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[143] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitPolicyAnalysisResponse.ProtoReflect.Descriptor instead. +func (*SubmitPolicyAnalysisResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{143} +} + +func (x *SubmitPolicyAnalysisResponse) GetAcceptedChunks() uint32 { + if x != nil { + return x.AcceptedChunks + } + return 0 +} + +func (x *SubmitPolicyAnalysisResponse) GetRejectedChunks() uint32 { + if x != nil { + return x.RejectedChunks + } + return 0 +} + +func (x *SubmitPolicyAnalysisResponse) GetRejectionReasons() []string { + if x != nil { + return x.RejectionReasons + } + return nil +} + +func (x *SubmitPolicyAnalysisResponse) GetAcceptedChunkIds() []string { + if x != nil { + return x.AcceptedChunkIds + } + return nil +} + +// Get draft policy for a sandbox. +type GetDraftPolicyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Optional status filter: "pending", "approved", "rejected", or "" for all. + StatusFilter string `protobuf:"bytes,2,opt,name=status_filter,json=statusFilter,proto3" json:"status_filter,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDraftPolicyRequest) Reset() { + *x = GetDraftPolicyRequest{} + mi := &file_openshell_proto_msgTypes[144] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDraftPolicyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDraftPolicyRequest) ProtoMessage() {} + +func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[144] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDraftPolicyRequest.ProtoReflect.Descriptor instead. +func (*GetDraftPolicyRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{144} +} + +func (x *GetDraftPolicyRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetDraftPolicyRequest) GetStatusFilter() string { + if x != nil { + return x.StatusFilter + } + return "" +} + +func (x *GetDraftPolicyRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +type GetDraftPolicyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Draft policy chunks. + Chunks []*PolicyChunk `protobuf:"bytes,1,rep,name=chunks,proto3" json:"chunks,omitempty"` + // LLM-generated summary of all analysis (empty in mechanistic mode). + RollingSummary string `protobuf:"bytes,2,opt,name=rolling_summary,json=rollingSummary,proto3" json:"rolling_summary,omitempty"` + // Current draft version. + DraftVersion uint64 `protobuf:"varint,3,opt,name=draft_version,json=draftVersion,proto3" json:"draft_version,omitempty"` + // When the last analysis completed (ms since epoch). + LastAnalyzedAtMs int64 `protobuf:"varint,4,opt,name=last_analyzed_at_ms,json=lastAnalyzedAtMs,proto3" json:"last_analyzed_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDraftPolicyResponse) Reset() { + *x = GetDraftPolicyResponse{} + mi := &file_openshell_proto_msgTypes[145] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDraftPolicyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDraftPolicyResponse) ProtoMessage() {} + +func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[145] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDraftPolicyResponse.ProtoReflect.Descriptor instead. +func (*GetDraftPolicyResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{145} +} + +func (x *GetDraftPolicyResponse) GetChunks() []*PolicyChunk { + if x != nil { + return x.Chunks + } + return nil +} + +func (x *GetDraftPolicyResponse) GetRollingSummary() string { + if x != nil { + return x.RollingSummary + } + return "" +} + +func (x *GetDraftPolicyResponse) GetDraftVersion() uint64 { + if x != nil { + return x.DraftVersion + } + return 0 +} + +func (x *GetDraftPolicyResponse) GetLastAnalyzedAtMs() int64 { + if x != nil { + return x.LastAnalyzedAtMs + } + return 0 +} + +// Approve a single draft chunk. +type ApproveDraftChunkRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Chunk ID to approve. + ChunkId string `protobuf:"bytes,2,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ApproveDraftChunkRequest) Reset() { + *x = ApproveDraftChunkRequest{} + mi := &file_openshell_proto_msgTypes[146] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApproveDraftChunkRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApproveDraftChunkRequest) ProtoMessage() {} + +func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[146] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApproveDraftChunkRequest.ProtoReflect.Descriptor instead. +func (*ApproveDraftChunkRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{146} +} + +func (x *ApproveDraftChunkRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ApproveDraftChunkRequest) GetChunkId() string { + if x != nil { + return x.ChunkId + } + return "" +} + +func (x *ApproveDraftChunkRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +type ApproveDraftChunkResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // New policy version after merge. + PolicyVersion uint32 `protobuf:"varint,1,opt,name=policy_version,json=policyVersion,proto3" json:"policy_version,omitempty"` + // SHA-256 hash of the new policy. + PolicyHash string `protobuf:"bytes,2,opt,name=policy_hash,json=policyHash,proto3" json:"policy_hash,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ApproveDraftChunkResponse) Reset() { + *x = ApproveDraftChunkResponse{} + mi := &file_openshell_proto_msgTypes[147] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApproveDraftChunkResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApproveDraftChunkResponse) ProtoMessage() {} + +func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[147] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApproveDraftChunkResponse.ProtoReflect.Descriptor instead. +func (*ApproveDraftChunkResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{147} +} + +func (x *ApproveDraftChunkResponse) GetPolicyVersion() uint32 { + if x != nil { + return x.PolicyVersion + } + return 0 +} + +func (x *ApproveDraftChunkResponse) GetPolicyHash() string { + if x != nil { + return x.PolicyHash + } + return "" +} + +// Reject a single draft chunk. +type RejectDraftChunkRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Chunk ID to reject. + ChunkId string `protobuf:"bytes,2,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,omitempty"` + // Optional reason for rejection (fed to LLM context in future analysis). + Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RejectDraftChunkRequest) Reset() { + *x = RejectDraftChunkRequest{} + mi := &file_openshell_proto_msgTypes[148] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RejectDraftChunkRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RejectDraftChunkRequest) ProtoMessage() {} + +func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[148] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RejectDraftChunkRequest.ProtoReflect.Descriptor instead. +func (*RejectDraftChunkRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{148} +} + +func (x *RejectDraftChunkRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RejectDraftChunkRequest) GetChunkId() string { + if x != nil { + return x.ChunkId + } + return "" +} + +func (x *RejectDraftChunkRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *RejectDraftChunkRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +type RejectDraftChunkResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RejectDraftChunkResponse) Reset() { + *x = RejectDraftChunkResponse{} + mi := &file_openshell_proto_msgTypes[149] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RejectDraftChunkResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RejectDraftChunkResponse) ProtoMessage() {} + +func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[149] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RejectDraftChunkResponse.ProtoReflect.Descriptor instead. +func (*RejectDraftChunkResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{149} +} + +// Approve all pending chunks. +type ApproveAllDraftChunksRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Include chunks with security_notes (default false: skips them). + IncludeSecurityFlagged bool `protobuf:"varint,2,opt,name=include_security_flagged,json=includeSecurityFlagged,proto3" json:"include_security_flagged,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ApproveAllDraftChunksRequest) Reset() { + *x = ApproveAllDraftChunksRequest{} + mi := &file_openshell_proto_msgTypes[150] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApproveAllDraftChunksRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApproveAllDraftChunksRequest) ProtoMessage() {} + +func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[150] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApproveAllDraftChunksRequest.ProtoReflect.Descriptor instead. +func (*ApproveAllDraftChunksRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{150} +} + +func (x *ApproveAllDraftChunksRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ApproveAllDraftChunksRequest) GetIncludeSecurityFlagged() bool { + if x != nil { + return x.IncludeSecurityFlagged + } + return false +} + +func (x *ApproveAllDraftChunksRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +type ApproveAllDraftChunksResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // New policy version after merge. + PolicyVersion uint32 `protobuf:"varint,1,opt,name=policy_version,json=policyVersion,proto3" json:"policy_version,omitempty"` + // SHA-256 hash of the new policy. + PolicyHash string `protobuf:"bytes,2,opt,name=policy_hash,json=policyHash,proto3" json:"policy_hash,omitempty"` + // Number of chunks approved. + ChunksApproved uint32 `protobuf:"varint,3,opt,name=chunks_approved,json=chunksApproved,proto3" json:"chunks_approved,omitempty"` + // Number of chunks skipped (security-flagged). + ChunksSkipped uint32 `protobuf:"varint,4,opt,name=chunks_skipped,json=chunksSkipped,proto3" json:"chunks_skipped,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ApproveAllDraftChunksResponse) Reset() { + *x = ApproveAllDraftChunksResponse{} + mi := &file_openshell_proto_msgTypes[151] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApproveAllDraftChunksResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApproveAllDraftChunksResponse) ProtoMessage() {} + +func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[151] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApproveAllDraftChunksResponse.ProtoReflect.Descriptor instead. +func (*ApproveAllDraftChunksResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{151} +} + +func (x *ApproveAllDraftChunksResponse) GetPolicyVersion() uint32 { + if x != nil { + return x.PolicyVersion + } + return 0 +} + +func (x *ApproveAllDraftChunksResponse) GetPolicyHash() string { + if x != nil { + return x.PolicyHash + } + return "" +} + +func (x *ApproveAllDraftChunksResponse) GetChunksApproved() uint32 { + if x != nil { + return x.ChunksApproved + } + return 0 +} + +func (x *ApproveAllDraftChunksResponse) GetChunksSkipped() uint32 { + if x != nil { + return x.ChunksSkipped + } + return 0 +} + +// Edit a pending chunk in-place. +type EditDraftChunkRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Chunk ID to edit. + ChunkId string `protobuf:"bytes,2,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,omitempty"` + // The modified rule (replaces existing proposed_rule). + ProposedRule *sandboxv1.NetworkPolicyRule `protobuf:"bytes,3,opt,name=proposed_rule,json=proposedRule,proto3" json:"proposed_rule,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EditDraftChunkRequest) Reset() { + *x = EditDraftChunkRequest{} + mi := &file_openshell_proto_msgTypes[152] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EditDraftChunkRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EditDraftChunkRequest) ProtoMessage() {} + +func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[152] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EditDraftChunkRequest.ProtoReflect.Descriptor instead. +func (*EditDraftChunkRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{152} +} + +func (x *EditDraftChunkRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *EditDraftChunkRequest) GetChunkId() string { + if x != nil { + return x.ChunkId + } + return "" +} + +func (x *EditDraftChunkRequest) GetProposedRule() *sandboxv1.NetworkPolicyRule { + if x != nil { + return x.ProposedRule + } + return nil +} + +func (x *EditDraftChunkRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +type EditDraftChunkResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EditDraftChunkResponse) Reset() { + *x = EditDraftChunkResponse{} + mi := &file_openshell_proto_msgTypes[153] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EditDraftChunkResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EditDraftChunkResponse) ProtoMessage() {} + +func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[153] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EditDraftChunkResponse.ProtoReflect.Descriptor instead. +func (*EditDraftChunkResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{153} +} + +// Reverse an approval (remove merged rule from active policy). +type UndoDraftChunkRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Chunk ID to undo. + ChunkId string `protobuf:"bytes,2,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UndoDraftChunkRequest) Reset() { + *x = UndoDraftChunkRequest{} + mi := &file_openshell_proto_msgTypes[154] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UndoDraftChunkRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UndoDraftChunkRequest) ProtoMessage() {} + +func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[154] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UndoDraftChunkRequest.ProtoReflect.Descriptor instead. +func (*UndoDraftChunkRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{154} +} + +func (x *UndoDraftChunkRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UndoDraftChunkRequest) GetChunkId() string { + if x != nil { + return x.ChunkId + } + return "" +} + +func (x *UndoDraftChunkRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +type UndoDraftChunkResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // New policy version after removal. + PolicyVersion uint32 `protobuf:"varint,1,opt,name=policy_version,json=policyVersion,proto3" json:"policy_version,omitempty"` + // SHA-256 hash of the updated policy. + PolicyHash string `protobuf:"bytes,2,opt,name=policy_hash,json=policyHash,proto3" json:"policy_hash,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UndoDraftChunkResponse) Reset() { + *x = UndoDraftChunkResponse{} + mi := &file_openshell_proto_msgTypes[155] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UndoDraftChunkResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UndoDraftChunkResponse) ProtoMessage() {} + +func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[155] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UndoDraftChunkResponse.ProtoReflect.Descriptor instead. +func (*UndoDraftChunkResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{155} +} + +func (x *UndoDraftChunkResponse) GetPolicyVersion() uint32 { + if x != nil { + return x.PolicyVersion + } + return 0 +} + +func (x *UndoDraftChunkResponse) GetPolicyHash() string { + if x != nil { + return x.PolicyHash + } + return "" +} + +// Clear all pending draft chunks for a sandbox. +type ClearDraftChunksRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClearDraftChunksRequest) Reset() { + *x = ClearDraftChunksRequest{} + mi := &file_openshell_proto_msgTypes[156] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClearDraftChunksRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClearDraftChunksRequest) ProtoMessage() {} + +func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[156] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClearDraftChunksRequest.ProtoReflect.Descriptor instead. +func (*ClearDraftChunksRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{156} +} + +func (x *ClearDraftChunksRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ClearDraftChunksRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +type ClearDraftChunksResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Number of chunks cleared. + ChunksCleared uint32 `protobuf:"varint,1,opt,name=chunks_cleared,json=chunksCleared,proto3" json:"chunks_cleared,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClearDraftChunksResponse) Reset() { + *x = ClearDraftChunksResponse{} + mi := &file_openshell_proto_msgTypes[157] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClearDraftChunksResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClearDraftChunksResponse) ProtoMessage() {} + +func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[157] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClearDraftChunksResponse.ProtoReflect.Descriptor instead. +func (*ClearDraftChunksResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{157} +} + +func (x *ClearDraftChunksResponse) GetChunksCleared() uint32 { + if x != nil { + return x.ChunksCleared + } + return 0 +} + +// Get decision history for a sandbox's draft policy. +type GetDraftHistoryRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDraftHistoryRequest) Reset() { + *x = GetDraftHistoryRequest{} + mi := &file_openshell_proto_msgTypes[158] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDraftHistoryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDraftHistoryRequest) ProtoMessage() {} + +func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[158] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDraftHistoryRequest.ProtoReflect.Descriptor instead. +func (*GetDraftHistoryRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{158} +} + +func (x *GetDraftHistoryRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetDraftHistoryRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +type DraftHistoryEntry struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Event timestamp (ms since epoch). + TimestampMs int64 `protobuf:"varint,1,opt,name=timestamp_ms,json=timestampMs,proto3" json:"timestamp_ms,omitempty"` + // Event type: "denial_detected", "analysis_cycle", "approved", + // "rejected", "edited", "undone", "cleared". + EventType string `protobuf:"bytes,2,opt,name=event_type,json=eventType,proto3" json:"event_type,omitempty"` + // Human-readable description. + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + // Associated chunk ID (if applicable). + ChunkId string `protobuf:"bytes,4,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DraftHistoryEntry) Reset() { + *x = DraftHistoryEntry{} + mi := &file_openshell_proto_msgTypes[159] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DraftHistoryEntry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DraftHistoryEntry) ProtoMessage() {} + +func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[159] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DraftHistoryEntry.ProtoReflect.Descriptor instead. +func (*DraftHistoryEntry) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{159} +} + +func (x *DraftHistoryEntry) GetTimestampMs() int64 { + if x != nil { + return x.TimestampMs + } + return 0 +} + +func (x *DraftHistoryEntry) GetEventType() string { + if x != nil { + return x.EventType + } + return "" +} + +func (x *DraftHistoryEntry) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *DraftHistoryEntry) GetChunkId() string { + if x != nil { + return x.ChunkId + } + return "" +} + +type GetDraftHistoryResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Chronological decision history. + Entries []*DraftHistoryEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDraftHistoryResponse) Reset() { + *x = GetDraftHistoryResponse{} + mi := &file_openshell_proto_msgTypes[160] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDraftHistoryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDraftHistoryResponse) ProtoMessage() {} + +func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[160] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDraftHistoryResponse.ProtoReflect.Descriptor instead. +func (*GetDraftHistoryResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{160} +} + +func (x *GetDraftHistoryResponse) GetEntries() []*DraftHistoryEntry { + if x != nil { + return x.Entries + } + return nil +} + +// Stored payload for a policy revision row in the generic objects table. +type PolicyRevisionPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Serialized policy contents. + Policy *sandboxv1.SandboxPolicy `protobuf:"bytes,1,opt,name=policy,proto3" json:"policy,omitempty"` + // Deterministic hash of the policy payload. + Hash string `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` + // Load error reported by the sandbox, if any. + LoadError string `protobuf:"bytes,3,opt,name=load_error,json=loadError,proto3" json:"load_error,omitempty"` + // When the policy version was reported as loaded (ms since epoch). 0 if unset. + LoadedAtMs int64 `protobuf:"varint,4,opt,name=loaded_at_ms,json=loadedAtMs,proto3" json:"loaded_at_ms,omitempty"` + // Immutable provenance supplied when this revision was created. + Provenance map[string]string `protobuf:"bytes,5,rep,name=provenance,proto3" json:"provenance,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyRevisionPayload) Reset() { + *x = PolicyRevisionPayload{} + mi := &file_openshell_proto_msgTypes[161] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyRevisionPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyRevisionPayload) ProtoMessage() {} + +func (x *PolicyRevisionPayload) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[161] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyRevisionPayload.ProtoReflect.Descriptor instead. +func (*PolicyRevisionPayload) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{161} +} + +func (x *PolicyRevisionPayload) GetPolicy() *sandboxv1.SandboxPolicy { + if x != nil { + return x.Policy + } + return nil +} + +func (x *PolicyRevisionPayload) GetHash() string { + if x != nil { + return x.Hash + } + return "" +} + +func (x *PolicyRevisionPayload) GetLoadError() string { + if x != nil { + return x.LoadError + } + return "" +} + +func (x *PolicyRevisionPayload) GetLoadedAtMs() int64 { + if x != nil { + return x.LoadedAtMs + } + return 0 +} + +func (x *PolicyRevisionPayload) GetProvenance() map[string]string { + if x != nil { + return x.Provenance + } + return nil +} + +// Stored payload for a draft policy chunk row in the generic objects table. +type DraftChunkPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Proposed network_policies map key. + RuleName string `protobuf:"bytes,1,opt,name=rule_name,json=ruleName,proto3" json:"rule_name,omitempty"` + // Proposed network policy rule. + ProposedRule *sandboxv1.NetworkPolicyRule `protobuf:"bytes,2,opt,name=proposed_rule,json=proposedRule,proto3" json:"proposed_rule,omitempty"` + // Human-readable explanation of why this rule is proposed. + Rationale string `protobuf:"bytes,3,opt,name=rationale,proto3" json:"rationale,omitempty"` + // Security concerns flagged by analysis (empty if none). + SecurityNotes string `protobuf:"bytes,4,opt,name=security_notes,json=securityNotes,proto3" json:"security_notes,omitempty"` + // Analysis confidence (0.0-1.0). 0 for mechanistic mode. + Confidence float32 `protobuf:"fixed32,5,opt,name=confidence,proto3" json:"confidence,omitempty"` + // When the user approved/rejected (ms since epoch). 0 if undecided. + DecidedAtMs int64 `protobuf:"varint,6,opt,name=decided_at_ms,json=decidedAtMs,proto3" json:"decided_at_ms,omitempty"` + // Denormalized endpoint host for dedup and display. + Host string `protobuf:"bytes,7,opt,name=host,proto3" json:"host,omitempty"` + // Denormalized endpoint port for dedup and display. + Port int32 `protobuf:"varint,8,opt,name=port,proto3" json:"port,omitempty"` + // Binary path that triggered the denial. + Binary string `protobuf:"bytes,9,opt,name=binary,proto3" json:"binary,omitempty"` + // Current draft version for the owning sandbox. + DraftVersion int64 `protobuf:"varint,10,opt,name=draft_version,json=draftVersion,proto3" json:"draft_version,omitempty"` + // Gateway prover verdict for this chunk; empty until prover runs. + // Mirrors PolicyChunk.validation_result. + ValidationResult string `protobuf:"bytes,11,opt,name=validation_result,json=validationResult,proto3" json:"validation_result,omitempty"` + // Operator-supplied free-form rejection text; empty for non-rejected + // chunks. Mirrors PolicyChunk.rejection_reason. + RejectionReason string `protobuf:"bytes,12,opt,name=rejection_reason,json=rejectionReason,proto3" json:"rejection_reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DraftChunkPayload) Reset() { + *x = DraftChunkPayload{} + mi := &file_openshell_proto_msgTypes[162] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DraftChunkPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DraftChunkPayload) ProtoMessage() {} + +func (x *DraftChunkPayload) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[162] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DraftChunkPayload.ProtoReflect.Descriptor instead. +func (*DraftChunkPayload) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{162} +} + +func (x *DraftChunkPayload) GetRuleName() string { + if x != nil { + return x.RuleName + } + return "" +} + +func (x *DraftChunkPayload) GetProposedRule() *sandboxv1.NetworkPolicyRule { + if x != nil { + return x.ProposedRule + } + return nil +} + +func (x *DraftChunkPayload) GetRationale() string { + if x != nil { + return x.Rationale + } + return "" +} + +func (x *DraftChunkPayload) GetSecurityNotes() string { + if x != nil { + return x.SecurityNotes + } + return "" +} + +func (x *DraftChunkPayload) GetConfidence() float32 { + if x != nil { + return x.Confidence + } + return 0 +} + +func (x *DraftChunkPayload) GetDecidedAtMs() int64 { + if x != nil { + return x.DecidedAtMs + } + return 0 +} + +func (x *DraftChunkPayload) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *DraftChunkPayload) GetPort() int32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *DraftChunkPayload) GetBinary() string { + if x != nil { + return x.Binary + } + return "" +} + +func (x *DraftChunkPayload) GetDraftVersion() int64 { + if x != nil { + return x.DraftVersion + } + return 0 +} + +func (x *DraftChunkPayload) GetValidationResult() string { + if x != nil { + return x.ValidationResult + } + return "" +} + +func (x *DraftChunkPayload) GetRejectionReason() string { + if x != nil { + return x.RejectionReason + } + return "" +} + +// Internal stored policy revision row materialized from the generic objects table. +type StoredPolicyRevision struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + SandboxId string `protobuf:"bytes,2,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + Version int64 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` + PolicyPayload []byte `protobuf:"bytes,4,opt,name=policy_payload,json=policyPayload,proto3" json:"policy_payload,omitempty"` + PolicyHash string `protobuf:"bytes,5,opt,name=policy_hash,json=policyHash,proto3" json:"policy_hash,omitempty"` + Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"` + LoadError *string `protobuf:"bytes,7,opt,name=load_error,json=loadError,proto3,oneof" json:"load_error,omitempty"` + CreatedAtMs int64 `protobuf:"varint,8,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + LoadedAtMs *int64 `protobuf:"varint,9,opt,name=loaded_at_ms,json=loadedAtMs,proto3,oneof" json:"loaded_at_ms,omitempty"` + Provenance map[string]string `protobuf:"bytes,10,rep,name=provenance,proto3" json:"provenance,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StoredPolicyRevision) Reset() { + *x = StoredPolicyRevision{} + mi := &file_openshell_proto_msgTypes[163] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StoredPolicyRevision) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StoredPolicyRevision) ProtoMessage() {} + +func (x *StoredPolicyRevision) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[163] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StoredPolicyRevision.ProtoReflect.Descriptor instead. +func (*StoredPolicyRevision) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{163} +} + +func (x *StoredPolicyRevision) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *StoredPolicyRevision) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *StoredPolicyRevision) GetVersion() int64 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *StoredPolicyRevision) GetPolicyPayload() []byte { + if x != nil { + return x.PolicyPayload + } + return nil +} + +func (x *StoredPolicyRevision) GetPolicyHash() string { + if x != nil { + return x.PolicyHash + } + return "" +} + +func (x *StoredPolicyRevision) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *StoredPolicyRevision) GetLoadError() string { + if x != nil && x.LoadError != nil { + return *x.LoadError + } + return "" +} + +func (x *StoredPolicyRevision) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *StoredPolicyRevision) GetLoadedAtMs() int64 { + if x != nil && x.LoadedAtMs != nil { + return *x.LoadedAtMs + } + return 0 +} + +func (x *StoredPolicyRevision) GetProvenance() map[string]string { + if x != nil { + return x.Provenance + } + return nil +} + +// Internal stored draft chunk row materialized from the generic objects table. +type StoredDraftChunk struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + SandboxId string `protobuf:"bytes,2,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + DraftVersion int64 `protobuf:"varint,3,opt,name=draft_version,json=draftVersion,proto3" json:"draft_version,omitempty"` + Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` + RuleName string `protobuf:"bytes,5,opt,name=rule_name,json=ruleName,proto3" json:"rule_name,omitempty"` + ProposedRule []byte `protobuf:"bytes,6,opt,name=proposed_rule,json=proposedRule,proto3" json:"proposed_rule,omitempty"` + Rationale string `protobuf:"bytes,7,opt,name=rationale,proto3" json:"rationale,omitempty"` + SecurityNotes string `protobuf:"bytes,8,opt,name=security_notes,json=securityNotes,proto3" json:"security_notes,omitempty"` + Confidence float64 `protobuf:"fixed64,9,opt,name=confidence,proto3" json:"confidence,omitempty"` + CreatedAtMs int64 `protobuf:"varint,10,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + DecidedAtMs *int64 `protobuf:"varint,11,opt,name=decided_at_ms,json=decidedAtMs,proto3,oneof" json:"decided_at_ms,omitempty"` + Host string `protobuf:"bytes,12,opt,name=host,proto3" json:"host,omitempty"` + Port int32 `protobuf:"varint,13,opt,name=port,proto3" json:"port,omitempty"` + Binary string `protobuf:"bytes,14,opt,name=binary,proto3" json:"binary,omitempty"` + HitCount int32 `protobuf:"varint,15,opt,name=hit_count,json=hitCount,proto3" json:"hit_count,omitempty"` + FirstSeenMs int64 `protobuf:"varint,16,opt,name=first_seen_ms,json=firstSeenMs,proto3" json:"first_seen_ms,omitempty"` + LastSeenMs int64 `protobuf:"varint,17,opt,name=last_seen_ms,json=lastSeenMs,proto3" json:"last_seen_ms,omitempty"` + // Gateway prover verdict; empty until the prover runs. See PolicyChunk. + ValidationResult string `protobuf:"bytes,18,opt,name=validation_result,json=validationResult,proto3" json:"validation_result,omitempty"` + // Operator-supplied free-form rejection text. See PolicyChunk. + RejectionReason string `protobuf:"bytes,19,opt,name=rejection_reason,json=rejectionReason,proto3" json:"rejection_reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StoredDraftChunk) Reset() { + *x = StoredDraftChunk{} + mi := &file_openshell_proto_msgTypes[164] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StoredDraftChunk) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StoredDraftChunk) ProtoMessage() {} + +func (x *StoredDraftChunk) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[164] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StoredDraftChunk.ProtoReflect.Descriptor instead. +func (*StoredDraftChunk) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{164} +} + +func (x *StoredDraftChunk) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *StoredDraftChunk) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *StoredDraftChunk) GetDraftVersion() int64 { + if x != nil { + return x.DraftVersion + } + return 0 +} + +func (x *StoredDraftChunk) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *StoredDraftChunk) GetRuleName() string { + if x != nil { + return x.RuleName + } + return "" +} + +func (x *StoredDraftChunk) GetProposedRule() []byte { + if x != nil { + return x.ProposedRule + } + return nil +} + +func (x *StoredDraftChunk) GetRationale() string { + if x != nil { + return x.Rationale + } + return "" +} + +func (x *StoredDraftChunk) GetSecurityNotes() string { + if x != nil { + return x.SecurityNotes + } + return "" +} + +func (x *StoredDraftChunk) GetConfidence() float64 { + if x != nil { + return x.Confidence + } + return 0 +} + +func (x *StoredDraftChunk) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *StoredDraftChunk) GetDecidedAtMs() int64 { + if x != nil && x.DecidedAtMs != nil { + return *x.DecidedAtMs + } + return 0 +} + +func (x *StoredDraftChunk) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *StoredDraftChunk) GetPort() int32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *StoredDraftChunk) GetBinary() string { + if x != nil { + return x.Binary + } + return "" +} + +func (x *StoredDraftChunk) GetHitCount() int32 { + if x != nil { + return x.HitCount + } + return 0 +} + +func (x *StoredDraftChunk) GetFirstSeenMs() int64 { + if x != nil { + return x.FirstSeenMs + } + return 0 +} + +func (x *StoredDraftChunk) GetLastSeenMs() int64 { + if x != nil { + return x.LastSeenMs + } + return 0 +} + +func (x *StoredDraftChunk) GetValidationResult() string { + if x != nil { + return x.ValidationResult + } + return "" +} + +func (x *StoredDraftChunk) GetRejectionReason() string { + if x != nil { + return x.RejectionReason + } + return "" +} + +// Create workspace request. +type CreateWorkspaceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace name. Must be a valid DNS-1123 label. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Optional labels for the workspace (key-value metadata). + Labels map[string]string `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateWorkspaceRequest) Reset() { + *x = CreateWorkspaceRequest{} + mi := &file_openshell_proto_msgTypes[165] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateWorkspaceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateWorkspaceRequest) ProtoMessage() {} + +func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[165] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateWorkspaceRequest.ProtoReflect.Descriptor instead. +func (*CreateWorkspaceRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{165} +} + +func (x *CreateWorkspaceRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateWorkspaceRequest) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +// Create workspace response. +type CreateWorkspaceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Workspace *datamodelv1.Workspace `protobuf:"bytes,1,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateWorkspaceResponse) Reset() { + *x = CreateWorkspaceResponse{} + mi := &file_openshell_proto_msgTypes[166] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateWorkspaceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateWorkspaceResponse) ProtoMessage() {} + +func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[166] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateWorkspaceResponse.ProtoReflect.Descriptor instead. +func (*CreateWorkspaceResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{166} +} + +func (x *CreateWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { + if x != nil { + return x.Workspace + } + return nil +} + +// Get workspace request. +type GetWorkspaceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace name (canonical lookup key). + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetWorkspaceRequest) Reset() { + *x = GetWorkspaceRequest{} + mi := &file_openshell_proto_msgTypes[167] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetWorkspaceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetWorkspaceRequest) ProtoMessage() {} + +func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[167] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetWorkspaceRequest.ProtoReflect.Descriptor instead. +func (*GetWorkspaceRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{167} +} + +func (x *GetWorkspaceRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// Get workspace response. +type GetWorkspaceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Workspace *datamodelv1.Workspace `protobuf:"bytes,1,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetWorkspaceResponse) Reset() { + *x = GetWorkspaceResponse{} + mi := &file_openshell_proto_msgTypes[168] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetWorkspaceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetWorkspaceResponse) ProtoMessage() {} + +func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[168] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetWorkspaceResponse.ProtoReflect.Descriptor instead. +func (*GetWorkspaceResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{168} +} + +func (x *GetWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { + if x != nil { + return x.Workspace + } + return nil +} + +// List workspaces request. +type ListWorkspacesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + // Optional label selector for filtering (format: "key1=value1,key2=value2"). + LabelSelector string `protobuf:"bytes,3,opt,name=label_selector,json=labelSelector,proto3" json:"label_selector,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListWorkspacesRequest) Reset() { + *x = ListWorkspacesRequest{} + mi := &file_openshell_proto_msgTypes[169] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListWorkspacesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListWorkspacesRequest) ProtoMessage() {} + +func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[169] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListWorkspacesRequest.ProtoReflect.Descriptor instead. +func (*ListWorkspacesRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{169} +} + +func (x *ListWorkspacesRequest) GetLimit() uint32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListWorkspacesRequest) GetOffset() uint32 { + if x != nil { + return x.Offset + } + return 0 +} + +func (x *ListWorkspacesRequest) GetLabelSelector() string { + if x != nil { + return x.LabelSelector + } + return "" +} + +// List workspaces response. +type ListWorkspacesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Workspaces []*datamodelv1.Workspace `protobuf:"bytes,1,rep,name=workspaces,proto3" json:"workspaces,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListWorkspacesResponse) Reset() { + *x = ListWorkspacesResponse{} + mi := &file_openshell_proto_msgTypes[170] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListWorkspacesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListWorkspacesResponse) ProtoMessage() {} + +func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[170] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListWorkspacesResponse.ProtoReflect.Descriptor instead. +func (*ListWorkspacesResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{170} +} + +func (x *ListWorkspacesResponse) GetWorkspaces() []*datamodelv1.Workspace { + if x != nil { + return x.Workspaces + } + return nil +} + +// Delete workspace request. +type DeleteWorkspaceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace name (canonical lookup key). + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteWorkspaceRequest) Reset() { + *x = DeleteWorkspaceRequest{} + mi := &file_openshell_proto_msgTypes[171] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteWorkspaceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteWorkspaceRequest) ProtoMessage() {} + +func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[171] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteWorkspaceRequest.ProtoReflect.Descriptor instead. +func (*DeleteWorkspaceRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{171} +} + +func (x *DeleteWorkspaceRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// Delete workspace response. +type DeleteWorkspaceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteWorkspaceResponse) Reset() { + *x = DeleteWorkspaceResponse{} + mi := &file_openshell_proto_msgTypes[172] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteWorkspaceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteWorkspaceResponse) ProtoMessage() {} + +func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[172] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteWorkspaceResponse.ProtoReflect.Descriptor instead. +func (*DeleteWorkspaceResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{172} +} + +func (x *DeleteWorkspaceResponse) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + +// Workspace membership record. +type WorkspaceMember struct { + state protoimpl.MessageState `protogen:"open.v1"` + Metadata *datamodelv1.ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + // OIDC subject claim identifying the principal. + PrincipalSubject string `protobuf:"bytes,2,opt,name=principal_subject,json=principalSubject,proto3" json:"principal_subject,omitempty"` + // Role assigned to the principal within the workspace. + Role WorkspaceRole `protobuf:"varint,3,opt,name=role,proto3,enum=openshell.v1.WorkspaceRole" json:"role,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WorkspaceMember) Reset() { + *x = WorkspaceMember{} + mi := &file_openshell_proto_msgTypes[173] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WorkspaceMember) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkspaceMember) ProtoMessage() {} + +func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[173] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WorkspaceMember.ProtoReflect.Descriptor instead. +func (*WorkspaceMember) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{173} +} + +func (x *WorkspaceMember) GetMetadata() *datamodelv1.ObjectMeta { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *WorkspaceMember) GetPrincipalSubject() string { + if x != nil { + return x.PrincipalSubject + } + return "" +} + +func (x *WorkspaceMember) GetRole() WorkspaceRole { + if x != nil { + return x.Role + } + return WorkspaceRole_WORKSPACE_ROLE_UNSPECIFIED +} + +// Add workspace member request. +type AddWorkspaceMemberRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace name. + Workspace string `protobuf:"bytes,1,opt,name=workspace,proto3" json:"workspace,omitempty"` + // OIDC subject claim identifying the principal. + PrincipalSubject string `protobuf:"bytes,2,opt,name=principal_subject,json=principalSubject,proto3" json:"principal_subject,omitempty"` + // Role to assign. + Role WorkspaceRole `protobuf:"varint,3,opt,name=role,proto3,enum=openshell.v1.WorkspaceRole" json:"role,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AddWorkspaceMemberRequest) Reset() { + *x = AddWorkspaceMemberRequest{} + mi := &file_openshell_proto_msgTypes[174] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddWorkspaceMemberRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddWorkspaceMemberRequest) ProtoMessage() {} + +func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[174] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddWorkspaceMemberRequest.ProtoReflect.Descriptor instead. +func (*AddWorkspaceMemberRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{174} +} + +func (x *AddWorkspaceMemberRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +func (x *AddWorkspaceMemberRequest) GetPrincipalSubject() string { + if x != nil { + return x.PrincipalSubject + } + return "" +} + +func (x *AddWorkspaceMemberRequest) GetRole() WorkspaceRole { + if x != nil { + return x.Role + } + return WorkspaceRole_WORKSPACE_ROLE_UNSPECIFIED +} + +// Add workspace member response. +type AddWorkspaceMemberResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Member *WorkspaceMember `protobuf:"bytes,1,opt,name=member,proto3" json:"member,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AddWorkspaceMemberResponse) Reset() { + *x = AddWorkspaceMemberResponse{} + mi := &file_openshell_proto_msgTypes[175] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddWorkspaceMemberResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddWorkspaceMemberResponse) ProtoMessage() {} + +func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[175] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddWorkspaceMemberResponse.ProtoReflect.Descriptor instead. +func (*AddWorkspaceMemberResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{175} +} + +func (x *AddWorkspaceMemberResponse) GetMember() *WorkspaceMember { + if x != nil { + return x.Member + } + return nil +} + +// Remove workspace member request. +type RemoveWorkspaceMemberRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace name. + Workspace string `protobuf:"bytes,1,opt,name=workspace,proto3" json:"workspace,omitempty"` + // OIDC subject claim identifying the principal to remove. + PrincipalSubject string `protobuf:"bytes,2,opt,name=principal_subject,json=principalSubject,proto3" json:"principal_subject,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveWorkspaceMemberRequest) Reset() { + *x = RemoveWorkspaceMemberRequest{} + mi := &file_openshell_proto_msgTypes[176] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveWorkspaceMemberRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveWorkspaceMemberRequest) ProtoMessage() {} + +func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[176] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveWorkspaceMemberRequest.ProtoReflect.Descriptor instead. +func (*RemoveWorkspaceMemberRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{176} +} + +func (x *RemoveWorkspaceMemberRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +func (x *RemoveWorkspaceMemberRequest) GetPrincipalSubject() string { + if x != nil { + return x.PrincipalSubject + } + return "" +} + +// Remove workspace member response. +type RemoveWorkspaceMemberResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Removed bool `protobuf:"varint,1,opt,name=removed,proto3" json:"removed,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveWorkspaceMemberResponse) Reset() { + *x = RemoveWorkspaceMemberResponse{} + mi := &file_openshell_proto_msgTypes[177] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveWorkspaceMemberResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveWorkspaceMemberResponse) ProtoMessage() {} + +func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[177] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveWorkspaceMemberResponse.ProtoReflect.Descriptor instead. +func (*RemoveWorkspaceMemberResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{177} +} + +func (x *RemoveWorkspaceMemberResponse) GetRemoved() bool { + if x != nil { + return x.Removed + } + return false +} + +// List workspace members request. +type ListWorkspaceMembersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace name. + Workspace string `protobuf:"bytes,1,opt,name=workspace,proto3" json:"workspace,omitempty"` + Limit uint32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListWorkspaceMembersRequest) Reset() { + *x = ListWorkspaceMembersRequest{} + mi := &file_openshell_proto_msgTypes[178] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListWorkspaceMembersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListWorkspaceMembersRequest) ProtoMessage() {} + +func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[178] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListWorkspaceMembersRequest.ProtoReflect.Descriptor instead. +func (*ListWorkspaceMembersRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{178} +} + +func (x *ListWorkspaceMembersRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +func (x *ListWorkspaceMembersRequest) GetLimit() uint32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListWorkspaceMembersRequest) GetOffset() uint32 { + if x != nil { + return x.Offset + } + return 0 +} + +// List workspace members response. +type ListWorkspaceMembersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Members []*WorkspaceMember `protobuf:"bytes,1,rep,name=members,proto3" json:"members,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListWorkspaceMembersResponse) Reset() { + *x = ListWorkspaceMembersResponse{} + mi := &file_openshell_proto_msgTypes[179] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListWorkspaceMembersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListWorkspaceMembersResponse) ProtoMessage() {} + +func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[179] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListWorkspaceMembersResponse.ProtoReflect.Descriptor instead. +func (*ListWorkspaceMembersResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{179} +} + +func (x *ListWorkspaceMembersResponse) GetMembers() []*WorkspaceMember { + if x != nil { + return x.Members + } + return nil +} + +var File_openshell_proto protoreflect.FileDescriptor + +const file_openshell_proto_rawDesc = "" + + "\n" + + "\x0fopenshell.proto\x12\fopenshell.v1\x1a\x0fdatamodel.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\roptions.proto\x1a\rsandbox.proto\"\x1a\n" + + "\x18IssueSandboxTokenRequest\"[\n" + + "\x19IssueSandboxTokenResponse\x12\x1a\n" + + "\x05token\x18\x01 \x01(\tB\x04\x88\xb5\x18\x01R\x05token\x12\"\n" + + "\rexpires_at_ms\x18\x02 \x01(\x03R\vexpiresAtMs\"\x1c\n" + + "\x1aRefreshSandboxTokenRequest\"]\n" + + "\x1bRefreshSandboxTokenResponse\x12\x1a\n" + + "\x05token\x18\x01 \x01(\tB\x04\x88\xb5\x18\x01R\x05token\x12\"\n" + + "\rexpires_at_ms\x18\x02 \x01(\x03R\vexpiresAtMs\"\x0f\n" + + "\rHealthRequest\"_\n" + + "\x0eHealthResponse\x123\n" + + "\x06status\x18\x01 \x01(\x0e2\x1b.openshell.v1.ServiceStatusR\x06status\x12\x18\n" + + "\aversion\x18\x02 \x01(\tR\aversion\"\x17\n" + + "\x15GetCurrentUserRequest\"\xb0\x01\n" + + "\x16GetCurrentUserResponse\x12\x18\n" + + "\asubject\x18\x01 \x01(\tR\asubject\x12!\n" + + "\fdisplay_name\x18\x02 \x01(\tR\vdisplayName\x12\x14\n" + + "\x05roles\x18\x03 \x03(\tR\x05roles\x12\x16\n" + + "\x06scopes\x18\x04 \x03(\tR\x06scopes\x12+\n" + + "\x11identity_provider\x18\x05 \x01(\tR\x10identityProvider\"\x17\n" + + "\x15GetGatewayInfoRequest\"\xc0\x01\n" + + "\x16GetGatewayInfoResponse\x123\n" + + "\x06status\x18\x01 \x01(\x0e2\x1b.openshell.v1.ServiceStatusR\x06status\x12'\n" + + "\x0fgateway_version\x18\x02 \x01(\tR\x0egatewayVersion\x12H\n" + + "\x0fcompute_drivers\x18\x03 \x03(\v2\x1f.openshell.v1.ComputeDriverInfoR\x0ecomputeDrivers\"t\n" + + "\x11ComputeDriverInfo\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12K\n" + + "\fcapabilities\x18\x02 \x01(\v2'.openshell.v1.ComputeDriverCapabilitiesR\fcapabilities\"c\n" + + "\x19ComputeDriverCapabilities\x12\x1f\n" + + "\vdriver_name\x18\x01 \x01(\tR\n" + + "driverName\x12%\n" + + "\x0edriver_version\x18\x02 \x01(\tR\rdriverVersion\"\xd8\x01\n" + + "\aSandbox\x12>\n" + + "\bmetadata\x18\x01 \x01(\v2\".openshell.datamodel.v1.ObjectMetaR\bmetadata\x12-\n" + + "\x04spec\x18\x02 \x01(\v2\x19.openshell.v1.SandboxSpecR\x04spec\x123\n" + + "\x06status\x18\x03 \x01(\v2\x1b.openshell.v1.SandboxStatusR\x06statusJ\x04\b\x04\x10\x05J\x04\b\x05\x10\x06R\x05phaseR\x16current_policy_version\"\xd7\x03\n" + + "\vSandboxSpec\x12\x1b\n" + + "\tlog_level\x18\x01 \x01(\tR\blogLevel\x12L\n" + + "\venvironment\x18\x05 \x03(\v2*.openshell.v1.SandboxSpec.EnvironmentEntryR\venvironment\x129\n" + + "\btemplate\x18\x06 \x01(\v2\x1d.openshell.v1.SandboxTemplateR\btemplate\x12;\n" + + "\x06policy\x18\a \x01(\v2#.openshell.sandbox.v1.SandboxPolicyR\x06policy\x12\x1c\n" + + "\tproviders\x18\b \x03(\tR\tproviders\x12W\n" + + "\x15resource_requirements\x18\t \x01(\v2\".openshell.v1.ResourceRequirementsR\x14resourceRequirements\x1a>\n" + + "\x10EnvironmentEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\n" + + "\x10\vJ\x04\b\v\x10\fR\n" + + "gpu_deviceR\x16proposal_approval_mode\"O\n" + + "\x14ResourceRequirements\x127\n" + + "\x03gpu\x18\x01 \x01(\v2%.openshell.v1.GpuResourceRequirementsR\x03gpu\">\n" + + "\x17GpuResourceRequirements\x12\x19\n" + + "\x05count\x18\x01 \x01(\rH\x00R\x05count\x88\x01\x01B\b\n" + + "\x06_count\"\xef\x05\n" + + "\x0fSandboxTemplate\x12\x14\n" + + "\x05image\x18\x01 \x01(\tR\x05image\x12,\n" + + "\x12runtime_class_name\x18\x02 \x01(\tR\x10runtimeClassName\x12!\n" + + "\fagent_socket\x18\x03 \x01(\tR\vagentSocket\x12A\n" + + "\x06labels\x18\x04 \x03(\v2).openshell.v1.SandboxTemplate.LabelsEntryR\x06labels\x12P\n" + + "\vannotations\x18\x05 \x03(\v2..openshell.v1.SandboxTemplate.AnnotationsEntryR\vannotations\x12P\n" + + "\venvironment\x18\x06 \x03(\v2..openshell.v1.SandboxTemplate.EnvironmentEntryR\venvironment\x125\n" + + "\tresources\x18\a \x01(\v2\x17.google.protobuf.StructR\tresources\x12,\n" + + "\x0fuser_namespaces\x18\n" + + " \x01(\bH\x00R\x0euserNamespaces\x88\x01\x01\x12<\n" + + "\rdriver_config\x18\v \x01(\v2\x17.google.protobuf.StructR\fdriverConfig\x1a9\n" + + "\vLabelsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a>\n" + + "\x10AnnotationsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a>\n" + + "\x10EnvironmentEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x12\n" + + "\x10_user_namespacesJ\x04\b\t\x10\n" + + "R\x16volume_claim_templates\"\xb1\x02\n" + + "\rSandboxStatus\x12!\n" + + "\fsandbox_name\x18\x01 \x01(\tR\vsandboxName\x12\x1b\n" + + "\tagent_pod\x18\x02 \x01(\tR\bagentPod\x12\x19\n" + + "\bagent_fd\x18\x03 \x01(\tR\aagentFd\x12\x1d\n" + + "\n" + + "sandbox_fd\x18\x04 \x01(\tR\tsandboxFd\x12>\n" + + "\n" + + "conditions\x18\x05 \x03(\v2\x1e.openshell.v1.SandboxConditionR\n" + + "conditions\x120\n" + + "\x05phase\x18\x06 \x01(\x0e2\x1a.openshell.v1.SandboxPhaseR\x05phase\x124\n" + + "\x16current_policy_version\x18\a \x01(\rR\x14currentPolicyVersion\"\xa2\x01\n" + + "\x10SandboxCondition\x12\x12\n" + + "\x04type\x18\x01 \x01(\tR\x04type\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\x12\x16\n" + + "\x06reason\x18\x03 \x01(\tR\x06reason\x12\x18\n" + + "\amessage\x18\x04 \x01(\tR\amessage\x120\n" + + "\x14last_transition_time\x18\x05 \x01(\tR\x12lastTransitionTime\"\x94\x02\n" + + "\rPlatformEvent\x12!\n" + + "\ftimestamp_ms\x18\x01 \x01(\x03R\vtimestampMs\x12\x16\n" + + "\x06source\x18\x02 \x01(\tR\x06source\x12\x12\n" + + "\x04type\x18\x03 \x01(\tR\x04type\x12\x16\n" + + "\x06reason\x18\x04 \x01(\tR\x06reason\x12\x18\n" + + "\amessage\x18\x05 \x01(\tR\amessage\x12E\n" + + "\bmetadata\x18\x06 \x03(\v2).openshell.v1.PlatformEvent.MetadataEntryR\bmetadata\x1a;\n" + + "\rMetadataEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x91\x03\n" + + "\x14CreateSandboxRequest\x12-\n" + + "\x04spec\x18\x01 \x01(\v2\x19.openshell.v1.SandboxSpecR\x04spec\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12F\n" + + "\x06labels\x18\x03 \x03(\v2..openshell.v1.CreateSandboxRequest.LabelsEntryR\x06labels\x12U\n" + + "\vannotations\x18\x04 \x03(\v23.openshell.v1.CreateSandboxRequest.AnnotationsEntryR\vannotations\x12\x1c\n" + + "\tworkspace\x18\x05 \x01(\tR\tworkspace\x1a9\n" + + "\vLabelsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a>\n" + + "\x10AnnotationsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"E\n" + + "\x11GetSandboxRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"\xb0\x01\n" + + "\x14ListSandboxesRequest\x12\x14\n" + + "\x05limit\x18\x01 \x01(\rR\x05limit\x12\x16\n" + + "\x06offset\x18\x02 \x01(\rR\x06offset\x12%\n" + + "\x0elabel_selector\x18\x03 \x01(\tR\rlabelSelector\x12\x1c\n" + + "\tworkspace\x18\x04 \x01(\tR\tworkspace\x12%\n" + + "\x0eall_workspaces\x18\x05 \x01(\bR\rallWorkspaces\"^\n" + + "\x1bListSandboxProvidersRequest\x12!\n" + + "\fsandbox_name\x18\x01 \x01(\tR\vsandboxName\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"\xc0\x01\n" + + "\x1cAttachSandboxProviderRequest\x12!\n" + + "\fsandbox_name\x18\x01 \x01(\tR\vsandboxName\x12#\n" + + "\rprovider_name\x18\x02 \x01(\tR\fproviderName\x12:\n" + + "\x19expected_resource_version\x18\x03 \x01(\x04R\x17expectedResourceVersion\x12\x1c\n" + + "\tworkspace\x18\x04 \x01(\tR\tworkspace\"\xc0\x01\n" + + "\x1cDetachSandboxProviderRequest\x12!\n" + + "\fsandbox_name\x18\x01 \x01(\tR\vsandboxName\x12#\n" + + "\rprovider_name\x18\x02 \x01(\tR\fproviderName\x12:\n" + + "\x19expected_resource_version\x18\x03 \x01(\x04R\x17expectedResourceVersion\x12\x1c\n" + + "\tworkspace\x18\x04 \x01(\tR\tworkspace\"H\n" + + "\x14DeleteSandboxRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"B\n" + + "\x0fSandboxResponse\x12/\n" + + "\asandbox\x18\x01 \x01(\v2\x15.openshell.v1.SandboxR\asandbox\"L\n" + + "\x15ListSandboxesResponse\x123\n" + + "\tsandboxes\x18\x01 \x03(\v2\x15.openshell.v1.SandboxR\tsandboxes\"^\n" + + "\x1cListSandboxProvidersResponse\x12>\n" + + "\tproviders\x18\x01 \x03(\v2 .openshell.datamodel.v1.ProviderR\tproviders\"l\n" + + "\x1dAttachSandboxProviderResponse\x12/\n" + + "\asandbox\x18\x01 \x01(\v2\x15.openshell.v1.SandboxR\asandbox\x12\x1a\n" + + "\battached\x18\x02 \x01(\bR\battached\"l\n" + + "\x1dDetachSandboxProviderResponse\x12/\n" + + "\asandbox\x18\x01 \x01(\v2\x15.openshell.v1.SandboxR\asandbox\x12\x1a\n" + + "\bdetached\x18\x02 \x01(\bR\bdetached\"1\n" + + "\x15DeleteSandboxResponse\x12\x18\n" + + "\adeleted\x18\x01 \x01(\bR\adeleted\"8\n" + + "\x17CreateSshSessionRequest\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\"\x98\x02\n" + + "\x18CreateSshSessionResponse\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x1a\n" + + "\x05token\x18\x02 \x01(\tB\x04\x88\xb5\x18\x01R\x05token\x12!\n" + + "\fgateway_host\x18\x03 \x01(\tR\vgatewayHost\x12!\n" + + "\fgateway_port\x18\x04 \x01(\rR\vgatewayPort\x12%\n" + + "\x0egateway_scheme\x18\x05 \x01(\tR\rgatewayScheme\x120\n" + + "\x14host_key_fingerprint\x18\a \x01(\tR\x12hostKeyFingerprint\x12\"\n" + + "\rexpires_at_ms\x18\b \x01(\x03R\vexpiresAtMs\"\xa1\x01\n" + + "\x14ExposeServiceRequest\x12\x18\n" + + "\asandbox\x18\x01 \x01(\tR\asandbox\x12\x18\n" + + "\aservice\x18\x02 \x01(\tR\aservice\x12\x1f\n" + + "\vtarget_port\x18\x03 \x01(\rR\n" + + "targetPort\x12\x16\n" + + "\x06domain\x18\x04 \x01(\bR\x06domain\x12\x1c\n" + + "\tworkspace\x18\x05 \x01(\tR\tworkspace\"e\n" + + "\x11GetServiceRequest\x12\x18\n" + + "\asandbox\x18\x01 \x01(\tR\asandbox\x12\x18\n" + + "\aservice\x18\x02 \x01(\tR\aservice\x12\x1c\n" + + "\tworkspace\x18\x03 \x01(\tR\tworkspace\"\xa2\x01\n" + + "\x13ListServicesRequest\x12\x18\n" + + "\asandbox\x18\x01 \x01(\tR\asandbox\x12\x14\n" + + "\x05limit\x18\x02 \x01(\rR\x05limit\x12\x16\n" + + "\x06offset\x18\x03 \x01(\rR\x06offset\x12\x1c\n" + + "\tworkspace\x18\x04 \x01(\tR\tworkspace\x12%\n" + + "\x0eall_workspaces\x18\x05 \x01(\bR\rallWorkspaces\"Y\n" + + "\x14ListServicesResponse\x12A\n" + + "\bservices\x18\x01 \x03(\v2%.openshell.v1.ServiceEndpointResponseR\bservices\"h\n" + + "\x14DeleteServiceRequest\x12\x18\n" + + "\asandbox\x18\x01 \x01(\tR\asandbox\x12\x18\n" + + "\aservice\x18\x02 \x01(\tR\aservice\x12\x1c\n" + + "\tworkspace\x18\x03 \x01(\tR\tworkspace\"1\n" + + "\x15DeleteServiceResponse\x12\x18\n" + + "\adeleted\x18\x01 \x01(\bR\adeleted\"\xef\x01\n" + + "\x0fServiceEndpoint\x12>\n" + + "\bmetadata\x18\x01 \x01(\v2\".openshell.datamodel.v1.ObjectMetaR\bmetadata\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x02 \x01(\tR\tsandboxId\x12!\n" + + "\fsandbox_name\x18\x03 \x01(\tR\vsandboxName\x12!\n" + + "\fservice_name\x18\x04 \x01(\tR\vserviceName\x12\x1f\n" + + "\vtarget_port\x18\x05 \x01(\rR\n" + + "targetPort\x12\x16\n" + + "\x06domain\x18\x06 \x01(\bR\x06domain\"f\n" + + "\x17ServiceEndpointResponse\x129\n" + + "\bendpoint\x18\x01 \x01(\v2\x1d.openshell.v1.ServiceEndpointR\bendpoint\x12\x10\n" + + "\x03url\x18\x02 \x01(\tR\x03url\"5\n" + + "\x17RevokeSshSessionRequest\x12\x1a\n" + + "\x05token\x18\x01 \x01(\tB\x04\x88\xb5\x18\x01R\x05token\"4\n" + + "\x18RevokeSshSessionResponse\x12\x18\n" + + "\arevoked\x18\x01 \x01(\bR\arevoked\"\xf5\x02\n" + + "\x12ExecSandboxRequest\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x18\n" + + "\acommand\x18\x02 \x03(\tR\acommand\x12\x18\n" + + "\aworkdir\x18\x03 \x01(\tR\aworkdir\x12S\n" + + "\venvironment\x18\x04 \x03(\v21.openshell.v1.ExecSandboxRequest.EnvironmentEntryR\venvironment\x12'\n" + + "\x0ftimeout_seconds\x18\x05 \x01(\rR\x0etimeoutSeconds\x12\x14\n" + + "\x05stdin\x18\x06 \x01(\fR\x05stdin\x12\x10\n" + + "\x03tty\x18\a \x01(\bR\x03tty\x12\x12\n" + + "\x04cols\x18\b \x01(\rR\x04cols\x12\x12\n" + + "\x04rows\x18\t \x01(\rR\x04rows\x1a>\n" + + "\x10EnvironmentEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"'\n" + + "\x11ExecSandboxStdout\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\"'\n" + + "\x11ExecSandboxStderr\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\".\n" + + "\x0fExecSandboxExit\x12\x1b\n" + + "\texit_code\x18\x01 \x01(\x05R\bexitCode\"\xc8\x01\n" + + "\x10ExecSandboxEvent\x129\n" + + "\x06stdout\x18\x01 \x01(\v2\x1f.openshell.v1.ExecSandboxStdoutH\x00R\x06stdout\x129\n" + + "\x06stderr\x18\x02 \x01(\v2\x1f.openshell.v1.ExecSandboxStderrH\x00R\x06stderr\x123\n" + + "\x04exit\x18\x03 \x01(\v2\x1d.openshell.v1.ExecSandboxExitH\x00R\x04exitB\t\n" + + "\apayload\"\xf3\x01\n" + + "\x0eTcpForwardInit\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x1d\n" + + "\n" + + "service_id\x18\x04 \x01(\tR\tserviceId\x120\n" + + "\x03ssh\x18\x05 \x01(\v2\x1c.openshell.v1.SshRelayTargetH\x00R\x03ssh\x120\n" + + "\x03tcp\x18\x06 \x01(\v2\x1c.openshell.v1.TcpRelayTargetH\x00R\x03tcp\x125\n" + + "\x13authorization_token\x18\a \x01(\tB\x04\x88\xb5\x18\x01R\x12authorizationTokenB\b\n" + + "\x06target\"f\n" + + "\x0fTcpForwardFrame\x122\n" + + "\x04init\x18\x01 \x01(\v2\x1c.openshell.v1.TcpForwardInitH\x00R\x04init\x12\x14\n" + + "\x04data\x18\x02 \x01(\fH\x00R\x04dataB\t\n" + + "\apayload\"\xb0\x01\n" + + "\x10ExecSandboxInput\x128\n" + + "\x05start\x18\x01 \x01(\v2 .openshell.v1.ExecSandboxRequestH\x00R\x05start\x12\x16\n" + + "\x05stdin\x18\x02 \x01(\fH\x00R\x05stdin\x12?\n" + + "\x06resize\x18\x03 \x01(\v2%.openshell.v1.ExecSandboxWindowResizeH\x00R\x06resizeB\t\n" + + "\apayload\"A\n" + + "\x17ExecSandboxWindowResize\x12\x12\n" + + "\x04cols\x18\x01 \x01(\rR\x04cols\x12\x12\n" + + "\x04rows\x18\x02 \x01(\rR\x04rows\"\xc5\x01\n" + + "\n" + + "SshSession\x12>\n" + + "\bmetadata\x18\x01 \x01(\v2\".openshell.datamodel.v1.ObjectMetaR\bmetadata\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x02 \x01(\tR\tsandboxId\x12\x1a\n" + + "\x05token\x18\x03 \x01(\tB\x04\x88\xb5\x18\x01R\x05token\x12\"\n" + + "\rexpires_at_ms\x18\x04 \x01(\x03R\vexpiresAtMs\x12\x18\n" + + "\arevoked\x18\x05 \x01(\bR\arevoked\"\xe6\x02\n" + + "\x13WatchSandboxRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12#\n" + + "\rfollow_status\x18\x02 \x01(\bR\ffollowStatus\x12\x1f\n" + + "\vfollow_logs\x18\x03 \x01(\bR\n" + + "followLogs\x12#\n" + + "\rfollow_events\x18\x04 \x01(\bR\ffollowEvents\x12$\n" + + "\x0elog_tail_lines\x18\x05 \x01(\rR\flogTailLines\x12\x1d\n" + + "\n" + + "event_tail\x18\x06 \x01(\rR\teventTail\x12(\n" + + "\x10stop_on_terminal\x18\a \x01(\bR\x0estopOnTerminal\x12 \n" + + "\flog_since_ms\x18\b \x01(\x03R\n" + + "logSinceMs\x12\x1f\n" + + "\vlog_sources\x18\t \x03(\tR\n" + + "logSources\x12\"\n" + + "\rlog_min_level\x18\n" + + " \x01(\tR\vlogMinLevel\"\xcc\x02\n" + + "\x12SandboxStreamEvent\x121\n" + + "\asandbox\x18\x01 \x01(\v2\x15.openshell.v1.SandboxH\x00R\asandbox\x120\n" + + "\x03log\x18\x02 \x01(\v2\x1c.openshell.v1.SandboxLogLineH\x00R\x03log\x123\n" + + "\x05event\x18\x03 \x01(\v2\x1b.openshell.v1.PlatformEventH\x00R\x05event\x12>\n" + + "\awarning\x18\x04 \x01(\v2\".openshell.v1.SandboxStreamWarningH\x00R\awarning\x12Q\n" + + "\x13draft_policy_update\x18\x05 \x01(\v2\x1f.openshell.v1.DraftPolicyUpdateH\x00R\x11draftPolicyUpdateB\t\n" + + "\apayload\"\xaf\x02\n" + + "\x0eSandboxLogLine\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12!\n" + + "\ftimestamp_ms\x18\x02 \x01(\x03R\vtimestampMs\x12\x14\n" + + "\x05level\x18\x03 \x01(\tR\x05level\x12\x16\n" + + "\x06target\x18\x04 \x01(\tR\x06target\x12\x18\n" + + "\amessage\x18\x05 \x01(\tR\amessage\x12\x16\n" + + "\x06source\x18\x06 \x01(\tR\x06source\x12@\n" + + "\x06fields\x18\a \x03(\v2(.openshell.v1.SandboxLogLine.FieldsEntryR\x06fields\x1a9\n" + + "\vFieldsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"0\n" + + "\x14SandboxStreamWarning\x12\x18\n" + + "\amessage\x18\x01 \x01(\tR\amessage\"s\n" + + "\x15CreateProviderRequest\x12<\n" + + "\bprovider\x18\x01 \x01(\v2 .openshell.datamodel.v1.ProviderR\bprovider\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"F\n" + + "\x12GetProviderRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"\x89\x01\n" + + "\x14ListProvidersRequest\x12\x14\n" + + "\x05limit\x18\x01 \x01(\rR\x05limit\x12\x16\n" + + "\x06offset\x18\x02 \x01(\rR\x06offset\x12\x1c\n" + + "\tworkspace\x18\x03 \x01(\tR\tworkspace\x12%\n" + + "\x0eall_workspaces\x18\x04 \x01(\bR\rallWorkspaces\"\xb6\x02\n" + + "\x15UpdateProviderRequest\x12<\n" + + "\bprovider\x18\x01 \x01(\v2 .openshell.datamodel.v1.ProviderR\bprovider\x12w\n" + + "\x18credential_expires_at_ms\x18\x02 \x03(\v2>.openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntryR\x15credentialExpiresAtMs\x12\x1c\n" + + "\tworkspace\x18\x03 \x01(\tR\tworkspace\x1aH\n" + + "\x1aCredentialExpiresAtMsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\x03R\x05value:\x028\x01\"I\n" + + "\x15DeleteProviderRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"P\n" + + "\x10ProviderResponse\x12<\n" + + "\bprovider\x18\x01 \x01(\v2 .openshell.datamodel.v1.ProviderR\bprovider\"W\n" + + "\x15ListProvidersResponse\x12>\n" + + "\tproviders\x18\x01 \x03(\v2 .openshell.datamodel.v1.ProviderR\tproviders\"i\n" + + "\x1bListProviderProfilesRequest\x12\x14\n" + + "\x05limit\x18\x01 \x01(\rR\x05limit\x12\x16\n" + + "\x06offset\x18\x02 \x01(\rR\x06offset\x12\x1c\n" + + "\tworkspace\x18\x03 \x01(\tR\tworkspace\"I\n" + + "\x19GetProviderProfileRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"l\n" + + "\x19ProviderProfileImportItem\x127\n" + + "\aprofile\x18\x01 \x01(\v2\x1d.openshell.v1.ProviderProfileR\aprofile\x12\x16\n" + + "\x06source\x18\x02 \x01(\tR\x06source\"\x9e\x01\n" + + "\x19ProviderProfileDiagnostic\x12\x16\n" + + "\x06source\x18\x01 \x01(\tR\x06source\x12\x1d\n" + + "\n" + + "profile_id\x18\x02 \x01(\tR\tprofileId\x12\x14\n" + + "\x05field\x18\x03 \x01(\tR\x05field\x12\x18\n" + + "\amessage\x18\x04 \x01(\tR\amessage\x12\x1a\n" + + "\bseverity\x18\x05 \x01(\tR\bseverity\"\x9e\x01\n" + + ",ProviderCredentialTokenGrantAudienceOverride\x12\x12\n" + + "\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\x02 \x01(\rR\x04port\x12\x12\n" + + "\x04path\x18\x03 \x01(\tR\x04path\x12\x1a\n" + + "\baudience\x18\x04 \x01(\tR\baudience\x12\x16\n" + + "\x06scopes\x18\x05 \x03(\tR\x06scopes\"\xf0\x02\n" + + "\x1cProviderCredentialTokenGrant\x12%\n" + + "\x0etoken_endpoint\x18\x01 \x01(\tR\rtokenEndpoint\x12\x1a\n" + + "\baudience\x18\x02 \x01(\tR\baudience\x12*\n" + + "\x11jwt_svid_audience\x18\x06 \x01(\tR\x0fjwtSvidAudience\x12\x16\n" + + "\x06scopes\x18\x03 \x03(\tR\x06scopes\x12*\n" + + "\x11cache_ttl_seconds\x18\x04 \x01(\x03R\x0fcacheTtlSeconds\x12i\n" + + "\x12audience_overrides\x18\x05 \x03(\v2:.openshell.v1.ProviderCredentialTokenGrantAudienceOverrideR\x11audienceOverrides\x122\n" + + "\x15client_assertion_type\x18\a \x01(\tR\x13clientAssertionType\"\x9e\x03\n" + + "\x19ProviderProfileCredential\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12 \n" + + "\vdescription\x18\x02 \x01(\tR\vdescription\x12\x19\n" + + "\benv_vars\x18\x03 \x03(\tR\aenvVars\x12\x1a\n" + + "\brequired\x18\x04 \x01(\bR\brequired\x12\x1d\n" + + "\n" + + "auth_style\x18\x05 \x01(\tR\tauthStyle\x12\x1f\n" + + "\vheader_name\x18\x06 \x01(\tR\n" + + "headerName\x12\x1f\n" + + "\vquery_param\x18\a \x01(\tR\n" + + "queryParam\x12A\n" + + "\arefresh\x18\b \x01(\v2'.openshell.v1.ProviderCredentialRefreshR\arefresh\x12#\n" + + "\rpath_template\x18\t \x01(\tR\fpathTemplate\x12K\n" + + "\vtoken_grant\x18\n" + + " \x01(\v2*.openshell.v1.ProviderCredentialTokenGrantR\n" + + "tokenGrant\"\x8d\x01\n" + + "!ProviderCredentialRefreshMaterial\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12 \n" + + "\vdescription\x18\x02 \x01(\tR\vdescription\x12\x1a\n" + + "\brequired\x18\x03 \x01(\bR\brequired\x12\x16\n" + + "\x06secret\x18\x04 \x01(\bR\x06secret\"Y\n" + + "\x1fProviderCredentialRefreshOutput\x12\x16\n" + + "\x06output\x18\x01 \x01(\tR\x06output\x12\x1e\n" + + "\n" + + "credential\x18\x02 \x01(\tR\n" + + "credential\"\xb0\x03\n" + + "\x19ProviderCredentialRefresh\x12K\n" + + "\bstrategy\x18\x01 \x01(\x0e2/.openshell.v1.ProviderCredentialRefreshStrategyR\bstrategy\x12\x1b\n" + + "\ttoken_url\x18\x02 \x01(\tR\btokenUrl\x12\x16\n" + + "\x06scopes\x18\x03 \x03(\tR\x06scopes\x124\n" + + "\x16refresh_before_seconds\x18\x04 \x01(\x03R\x14refreshBeforeSeconds\x120\n" + + "\x14max_lifetime_seconds\x18\x05 \x01(\x03R\x12maxLifetimeSeconds\x12K\n" + + "\bmaterial\x18\x06 \x03(\v2/.openshell.v1.ProviderCredentialRefreshMaterialR\bmaterial\x12\\\n" + + "\x12additional_outputs\x18\a \x03(\v2-.openshell.v1.ProviderCredentialRefreshOutputR\x11additionalOutputs\"\x90\x03\n" + + "\x1fProviderCredentialRefreshStatus\x12#\n" + + "\rprovider_name\x18\x01 \x01(\tR\fproviderName\x12\x1f\n" + + "\vprovider_id\x18\x02 \x01(\tR\n" + + "providerId\x12%\n" + + "\x0ecredential_key\x18\x03 \x01(\tR\rcredentialKey\x12K\n" + + "\bstrategy\x18\x04 \x01(\x0e2/.openshell.v1.ProviderCredentialRefreshStrategyR\bstrategy\x12\x16\n" + + "\x06status\x18\x05 \x01(\tR\x06status\x12\"\n" + + "\rexpires_at_ms\x18\x06 \x01(\x03R\vexpiresAtMs\x12+\n" + + "\x12next_refresh_at_ms\x18\a \x01(\x03R\x0fnextRefreshAtMs\x12+\n" + + "\x12last_refresh_at_ms\x18\b \x01(\x03R\x0flastRefreshAtMs\x12\x1d\n" + + "\n" + + "last_error\x18\t \x01(\tR\tlastError\"<\n" + + "\x18ProviderProfileDiscovery\x12 \n" + + "\vcredentials\x18\x01 \x03(\tR\vcredentials\"\x93\b\n" + + "$StoredProviderCredentialRefreshState\x12>\n" + + "\bmetadata\x18\x01 \x01(\v2\".openshell.datamodel.v1.ObjectMetaR\bmetadata\x12\x1f\n" + + "\vprovider_id\x18\x02 \x01(\tR\n" + + "providerId\x12#\n" + + "\rprovider_name\x18\x03 \x01(\tR\fproviderName\x12%\n" + + "\x0ecredential_key\x18\x04 \x01(\tR\rcredentialKey\x12K\n" + + "\bstrategy\x18\x05 \x01(\x0e2/.openshell.v1.ProviderCredentialRefreshStrategyR\bstrategy\x12b\n" + + "\bmaterial\x18\x06 \x03(\v2@.openshell.v1.StoredProviderCredentialRefreshState.MaterialEntryB\x04\x88\xb5\x18\x01R\bmaterial\x120\n" + + "\x14secret_material_keys\x18\a \x03(\tR\x12secretMaterialKeys\x12\"\n" + + "\rexpires_at_ms\x18\b \x01(\x03R\vexpiresAtMs\x12+\n" + + "\x12next_refresh_at_ms\x18\t \x01(\x03R\x0fnextRefreshAtMs\x12+\n" + + "\x12last_refresh_at_ms\x18\n" + + " \x01(\x03R\x0flastRefreshAtMs\x12\x16\n" + + "\x06status\x18\v \x01(\tR\x06status\x12\x1d\n" + + "\n" + + "last_error\x18\f \x01(\tR\tlastError\x12\x1b\n" + + "\ttoken_url\x18\r \x01(\tR\btokenUrl\x12\x16\n" + + "\x06scopes\x18\x0e \x03(\tR\x06scopes\x124\n" + + "\x16refresh_before_seconds\x18\x0f \x01(\x03R\x14refreshBeforeSeconds\x120\n" + + "\x14max_lifetime_seconds\x18\x10 \x01(\x03R\x12maxLifetimeSeconds\x12\x82\x01\n" + + "\x16additional_output_keys\x18\x11 \x03(\v2L.openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntryR\x14additionalOutputKeys\x1a;\n" + + "\rMaterialEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1aG\n" + + "\x19AdditionalOutputKeysEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x82\x01\n" + + "\x1fGetProviderRefreshStatusRequest\x12\x1a\n" + + "\bprovider\x18\x01 \x01(\tR\bprovider\x12%\n" + + "\x0ecredential_key\x18\x02 \x01(\tR\rcredentialKey\x12\x1c\n" + + "\tworkspace\x18\x03 \x01(\tR\tworkspace\"s\n" + + " GetProviderRefreshStatusResponse\x12O\n" + + "\vcredentials\x18\x01 \x03(\v2-.openshell.v1.ProviderCredentialRefreshStatusR\vcredentials\"\xd8\x03\n" + + "\x1fConfigureProviderRefreshRequest\x12\x1a\n" + + "\bprovider\x18\x01 \x01(\tR\bprovider\x12%\n" + + "\x0ecredential_key\x18\x02 \x01(\tR\rcredentialKey\x12K\n" + + "\bstrategy\x18\x03 \x01(\x0e2/.openshell.v1.ProviderCredentialRefreshStrategyR\bstrategy\x12]\n" + + "\bmaterial\x18\x04 \x03(\v2;.openshell.v1.ConfigureProviderRefreshRequest.MaterialEntryB\x04\x88\xb5\x18\x01R\bmaterial\x120\n" + + "\x14secret_material_keys\x18\x05 \x03(\tR\x12secretMaterialKeys\x12'\n" + + "\rexpires_at_ms\x18\x06 \x01(\x03H\x00R\vexpiresAtMs\x88\x01\x01\x12\x1c\n" + + "\tworkspace\x18\a \x01(\tR\tworkspace\x1a;\n" + + "\rMaterialEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x10\n" + + "\x0e_expires_at_ms\"i\n" + + " ConfigureProviderRefreshResponse\x12E\n" + + "\x06status\x18\x01 \x01(\v2-.openshell.v1.ProviderCredentialRefreshStatusR\x06status\"\x82\x01\n" + + "\x1fRotateProviderCredentialRequest\x12\x1a\n" + + "\bprovider\x18\x01 \x01(\tR\bprovider\x12%\n" + + "\x0ecredential_key\x18\x02 \x01(\tR\rcredentialKey\x12\x1c\n" + + "\tworkspace\x18\x03 \x01(\tR\tworkspace\"i\n" + + " RotateProviderCredentialResponse\x12E\n" + + "\x06status\x18\x01 \x01(\v2-.openshell.v1.ProviderCredentialRefreshStatusR\x06status\"\x7f\n" + + "\x1cDeleteProviderRefreshRequest\x12\x1a\n" + + "\bprovider\x18\x01 \x01(\tR\bprovider\x12%\n" + + "\x0ecredential_key\x18\x02 \x01(\tR\rcredentialKey\x12\x1c\n" + + "\tworkspace\x18\x03 \x01(\tR\tworkspace\"9\n" + + "\x1dDeleteProviderRefreshResponse\x12\x18\n" + + "\adeleted\x18\x01 \x01(\bR\adeleted\"\xd8\x05\n" + + "\x0fProviderProfile\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12!\n" + + "\fdisplay_name\x18\x02 \x01(\tR\vdisplayName\x12 \n" + + "\vdescription\x18\x03 \x01(\tR\vdescription\x12A\n" + + "\bcategory\x18\x04 \x01(\x0e2%.openshell.v1.ProviderProfileCategoryR\bcategory\x12I\n" + + "\vcredentials\x18\x05 \x03(\v2'.openshell.v1.ProviderProfileCredentialR\vcredentials\x12C\n" + + "\tendpoints\x18\x06 \x03(\v2%.openshell.sandbox.v1.NetworkEndpointR\tendpoints\x12?\n" + + "\bbinaries\x18\a \x03(\v2#.openshell.sandbox.v1.NetworkBinaryR\bbinaries\x12+\n" + + "\x11inference_capable\x18\b \x01(\bR\x10inferenceCapable\x12D\n" + + "\tdiscovery\x18\t \x01(\v2&.openshell.v1.ProviderProfileDiscoveryR\tdiscovery\x12)\n" + + "\x10resource_version\x18\n" + + " \x01(\x04R\x0fresourceVersion\x12P\n" + + "\vannotations\x18\v \x03(\v2..openshell.v1.ProviderProfile.AnnotationsEntryR\vannotations\x12\x16\n" + + "\x06source\x18\f \x01(\tR\x06source\x12\x14\n" + + "\x05scope\x18\r \x01(\tR\x05scope\x1a>\n" + + "\x10AnnotationsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x90\x01\n" + + "\x15StoredProviderProfile\x12>\n" + + "\bmetadata\x18\x01 \x01(\v2\".openshell.datamodel.v1.ObjectMetaR\bmetadata\x127\n" + + "\aprofile\x18\x02 \x01(\v2\x1d.openshell.v1.ProviderProfileR\aprofile\"R\n" + + "\x17ProviderProfileResponse\x127\n" + + "\aprofile\x18\x01 \x01(\v2\x1d.openshell.v1.ProviderProfileR\aprofile\"Y\n" + + "\x1cListProviderProfilesResponse\x129\n" + + "\bprofiles\x18\x01 \x03(\v2\x1d.openshell.v1.ProviderProfileR\bprofiles\"\x82\x01\n" + + "\x1dImportProviderProfilesRequest\x12C\n" + + "\bprofiles\x18\x01 \x03(\v2'.openshell.v1.ProviderProfileImportItemR\bprofiles\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"\xc2\x01\n" + + "\x1eImportProviderProfilesResponse\x12I\n" + + "\vdiagnostics\x18\x01 \x03(\v2'.openshell.v1.ProviderProfileDiagnosticR\vdiagnostics\x129\n" + + "\bprofiles\x18\x02 \x03(\v2\x1d.openshell.v1.ProviderProfileR\bprofiles\x12\x1a\n" + + "\bimported\x18\x03 \x01(\bR\bimported\"\xcc\x01\n" + + "\x1dUpdateProviderProfilesRequest\x12A\n" + + "\aprofile\x18\x01 \x01(\v2'.openshell.v1.ProviderProfileImportItemR\aprofile\x12:\n" + + "\x19expected_resource_version\x18\x02 \x01(\x04R\x17expectedResourceVersion\x12\x0e\n" + + "\x02id\x18\x03 \x01(\tR\x02id\x12\x1c\n" + + "\tworkspace\x18\x04 \x01(\tR\tworkspace\"\xbe\x01\n" + + "\x1eUpdateProviderProfilesResponse\x12I\n" + + "\vdiagnostics\x18\x01 \x03(\v2'.openshell.v1.ProviderProfileDiagnosticR\vdiagnostics\x127\n" + + "\aprofile\x18\x02 \x01(\v2\x1d.openshell.v1.ProviderProfileR\aprofile\x12\x18\n" + + "\aupdated\x18\x03 \x01(\bR\aupdated\"\x80\x01\n" + + "\x1bLintProviderProfilesRequest\x12C\n" + + "\bprofiles\x18\x01 \x03(\v2'.openshell.v1.ProviderProfileImportItemR\bprofiles\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"\x7f\n" + + "\x1cLintProviderProfilesResponse\x12I\n" + + "\vdiagnostics\x18\x01 \x03(\v2'.openshell.v1.ProviderProfileDiagnosticR\vdiagnostics\x12\x14\n" + + "\x05valid\x18\x02 \x01(\bR\x05valid\"2\n" + + "\x16DeleteProviderResponse\x12\x18\n" + + "\adeleted\x18\x01 \x01(\bR\adeleted\"L\n" + + "\x1cDeleteProviderProfileRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"9\n" + + "\x1dDeleteProviderProfileResponse\x12\x18\n" + + "\adeleted\x18\x01 \x01(\bR\adeleted\"E\n" + + "$GetSandboxProviderEnvironmentRequest\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\"\xcb\x05\n" + + "%GetSandboxProviderEnvironmentResponse\x12l\n" + + "\venvironment\x18\x01 \x03(\v2D.openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntryB\x04\x88\xb5\x18\x01R\venvironment\x122\n" + + "\x15provider_env_revision\x18\x02 \x01(\x04R\x13providerEnvRevision\x12\x87\x01\n" + + "\x18credential_expires_at_ms\x18\x03 \x03(\v2N.openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntryR\x15credentialExpiresAtMs\x12|\n" + + "\x13dynamic_credentials\x18\x04 \x03(\v2K.openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntryR\x12dynamicCredentials\x1a>\n" + + "\x10EnvironmentEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1aH\n" + + "\x1aCredentialExpiresAtMsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\x03R\x05value:\x028\x01\x1an\n" + + "\x17DynamicCredentialsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12=\n" + + "\x05value\x18\x02 \x01(\v2'.openshell.v1.ProviderProfileCredentialR\x05value:\x028\x01\"\xce\x04\n" + + "\x13UpdateConfigRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12;\n" + + "\x06policy\x18\x02 \x01(\v2#.openshell.sandbox.v1.SandboxPolicyR\x06policy\x12\x1f\n" + + "\vsetting_key\x18\x03 \x01(\tR\n" + + "settingKey\x12G\n" + + "\rsetting_value\x18\x04 \x01(\v2\".openshell.sandbox.v1.SettingValueR\fsettingValue\x12%\n" + + "\x0edelete_setting\x18\x05 \x01(\bR\rdeleteSetting\x12\x16\n" + + "\x06global\x18\x06 \x01(\bR\x06global\x12M\n" + + "\x10merge_operations\x18\a \x03(\v2\".openshell.v1.PolicyMergeOperationR\x0fmergeOperations\x12:\n" + + "\x19expected_resource_version\x18\b \x01(\x04R\x17expectedResourceVersion\x12T\n" + + "\vannotations\x18\t \x03(\v22.openshell.v1.UpdateConfigRequest.AnnotationsEntryR\vannotations\x12\x1c\n" + + "\tworkspace\x18\n" + + " \x01(\tR\tworkspace\x1a>\n" + + "\x10AnnotationsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc7\x03\n" + + "\x14PolicyMergeOperation\x129\n" + + "\badd_rule\x18\x01 \x01(\v2\x1c.openshell.v1.AddNetworkRuleH\x00R\aaddRule\x12N\n" + + "\x0fremove_endpoint\x18\x02 \x01(\v2#.openshell.v1.RemoveNetworkEndpointH\x00R\x0eremoveEndpoint\x12B\n" + + "\vremove_rule\x18\x03 \x01(\v2\x1f.openshell.v1.RemoveNetworkRuleH\x00R\n" + + "removeRule\x12B\n" + + "\x0eadd_deny_rules\x18\x04 \x01(\v2\x1a.openshell.v1.AddDenyRulesH\x00R\faddDenyRules\x12E\n" + + "\x0fadd_allow_rules\x18\x05 \x01(\v2\x1b.openshell.v1.AddAllowRulesH\x00R\raddAllowRules\x12H\n" + + "\rremove_binary\x18\x06 \x01(\v2!.openshell.v1.RemoveNetworkBinaryH\x00R\fremoveBinaryB\v\n" + + "\toperation\"j\n" + + "\x0eAddNetworkRule\x12\x1b\n" + + "\trule_name\x18\x01 \x01(\tR\bruleName\x12;\n" + + "\x04rule\x18\x02 \x01(\v2'.openshell.sandbox.v1.NetworkPolicyRuleR\x04rule\"\\\n" + + "\x15RemoveNetworkEndpoint\x12\x1b\n" + + "\trule_name\x18\x01 \x01(\tR\bruleName\x12\x12\n" + + "\x04host\x18\x02 \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\x03 \x01(\rR\x04port\"0\n" + + "\x11RemoveNetworkRule\x12\x1b\n" + + "\trule_name\x18\x01 \x01(\tR\bruleName\"w\n" + + "\fAddDenyRules\x12\x12\n" + + "\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\x02 \x01(\rR\x04port\x12?\n" + + "\n" + + "deny_rules\x18\x03 \x03(\v2 .openshell.sandbox.v1.L7DenyRuleR\tdenyRules\"k\n" + + "\rAddAllowRules\x12\x12\n" + + "\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\x02 \x01(\rR\x04port\x122\n" + + "\x05rules\x18\x03 \x03(\v2\x1c.openshell.sandbox.v1.L7RuleR\x05rules\"S\n" + + "\x13RemoveNetworkBinary\x12\x1b\n" + + "\trule_name\x18\x01 \x01(\tR\bruleName\x12\x1f\n" + + "\vbinary_path\x18\x02 \x01(\tR\n" + + "binaryPath\"\xaf\x02\n" + + "\x14UpdateConfigResponse\x12\x18\n" + + "\aversion\x18\x01 \x01(\rR\aversion\x12\x1f\n" + + "\vpolicy_hash\x18\x02 \x01(\tR\n" + + "policyHash\x12+\n" + + "\x11settings_revision\x18\x03 \x01(\x04R\x10settingsRevision\x12\x18\n" + + "\adeleted\x18\x04 \x01(\bR\adeleted\x12U\n" + + "\vannotations\x18\x05 \x03(\v23.openshell.v1.UpdateConfigResponse.AnnotationsEntryR\vannotations\x1a>\n" + + "\x10AnnotationsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x83\x01\n" + + "\x1dGetSandboxPolicyStatusRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\aversion\x18\x02 \x01(\rR\aversion\x12\x16\n" + + "\x06global\x18\x03 \x01(\bR\x06global\x12\x1c\n" + + "\tworkspace\x18\x04 \x01(\tR\tworkspace\"\x88\x01\n" + + "\x1eGetSandboxPolicyStatusResponse\x12?\n" + + "\brevision\x18\x01 \x01(\v2#.openshell.v1.SandboxPolicyRevisionR\brevision\x12%\n" + + "\x0eactive_version\x18\x02 \x01(\rR\ractiveVersion\"\x94\x01\n" + + "\x1aListSandboxPoliciesRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + + "\x05limit\x18\x02 \x01(\rR\x05limit\x12\x16\n" + + "\x06offset\x18\x03 \x01(\rR\x06offset\x12\x16\n" + + "\x06global\x18\x04 \x01(\bR\x06global\x12\x1c\n" + + "\tworkspace\x18\x05 \x01(\tR\tworkspace\"`\n" + + "\x1bListSandboxPoliciesResponse\x12A\n" + + "\trevisions\x18\x01 \x03(\v2#.openshell.v1.SandboxPolicyRevisionR\trevisions\"\xa7\x01\n" + + "\x19ReportPolicyStatusRequest\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x18\n" + + "\aversion\x18\x02 \x01(\rR\aversion\x122\n" + + "\x06status\x18\x03 \x01(\x0e2\x1a.openshell.v1.PolicyStatusR\x06status\x12\x1d\n" + + "\n" + + "load_error\x18\x04 \x01(\tR\tloadError\"\x1c\n" + + "\x1aReportPolicyStatusResponse\"\xbc\x03\n" + + "\x15SandboxPolicyRevision\x12\x18\n" + + "\aversion\x18\x01 \x01(\rR\aversion\x12\x1f\n" + + "\vpolicy_hash\x18\x02 \x01(\tR\n" + + "policyHash\x122\n" + + "\x06status\x18\x03 \x01(\x0e2\x1a.openshell.v1.PolicyStatusR\x06status\x12\x1d\n" + + "\n" + + "load_error\x18\x04 \x01(\tR\tloadError\x12\"\n" + + "\rcreated_at_ms\x18\x05 \x01(\x03R\vcreatedAtMs\x12 \n" + + "\floaded_at_ms\x18\x06 \x01(\x03R\n" + + "loadedAtMs\x12;\n" + + "\x06policy\x18\a \x01(\v2#.openshell.sandbox.v1.SandboxPolicyR\x06policy\x12S\n" + + "\n" + + "provenance\x18\b \x03(\v23.openshell.v1.SandboxPolicyRevision.ProvenanceEntryR\n" + + "provenance\x1a=\n" + + "\x0fProvenanceEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xbc\x01\n" + + "\x15GetSandboxLogsRequest\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x14\n" + + "\x05lines\x18\x02 \x01(\rR\x05lines\x12\x19\n" + + "\bsince_ms\x18\x03 \x01(\x03R\asinceMs\x12\x18\n" + + "\asources\x18\x04 \x03(\tR\asources\x12\x1b\n" + + "\tmin_level\x18\x05 \x01(\tR\bminLevel\x12\x1c\n" + + "\tworkspace\x18\x06 \x01(\tR\tworkspace\"i\n" + + "\x16PushSandboxLogsRequest\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x120\n" + + "\x04logs\x18\x02 \x03(\v2\x1c.openshell.v1.SandboxLogLineR\x04logs\"\x19\n" + + "\x17PushSandboxLogsResponse\"m\n" + + "\x16GetSandboxLogsResponse\x120\n" + + "\x04logs\x18\x01 \x03(\v2\x1c.openshell.v1.SandboxLogLineR\x04logs\x12!\n" + + "\fbuffer_total\x18\x02 \x01(\rR\vbufferTotal\"\xa2\x02\n" + + "\x11SupervisorMessage\x125\n" + + "\x05hello\x18\x01 \x01(\v2\x1d.openshell.v1.SupervisorHelloH\x00R\x05hello\x12A\n" + + "\theartbeat\x18\x02 \x01(\v2!.openshell.v1.SupervisorHeartbeatH\x00R\theartbeat\x12K\n" + + "\x11relay_open_result\x18\x03 \x01(\v2\x1d.openshell.v1.RelayOpenResultH\x00R\x0frelayOpenResult\x12;\n" + + "\vrelay_close\x18\x04 \x01(\v2\x18.openshell.v1.RelayCloseH\x00R\n" + + "relayCloseB\t\n" + + "\apayload\"\xea\x02\n" + + "\x0eGatewayMessage\x12J\n" + + "\x10session_accepted\x18\x01 \x01(\v2\x1d.openshell.v1.SessionAcceptedH\x00R\x0fsessionAccepted\x12J\n" + + "\x10session_rejected\x18\x02 \x01(\v2\x1d.openshell.v1.SessionRejectedH\x00R\x0fsessionRejected\x12>\n" + + "\theartbeat\x18\x03 \x01(\v2\x1e.openshell.v1.GatewayHeartbeatH\x00R\theartbeat\x128\n" + + "\n" + + "relay_open\x18\x04 \x01(\v2\x17.openshell.v1.RelayOpenH\x00R\trelayOpen\x12;\n" + + "\vrelay_close\x18\x05 \x01(\v2\x18.openshell.v1.RelayCloseH\x00R\n" + + "relayCloseB\t\n" + + "\apayload\"Q\n" + + "\x0fSupervisorHello\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x1f\n" + + "\vinstance_id\x18\x02 \x01(\tR\n" + + "instanceId\"h\n" + + "\x0fSessionAccepted\x12\x1d\n" + + "\n" + + "session_id\x18\x01 \x01(\tR\tsessionId\x126\n" + + "\x17heartbeat_interval_secs\x18\x02 \x01(\rR\x15heartbeatIntervalSecs\")\n" + + "\x0fSessionRejected\x12\x16\n" + + "\x06reason\x18\x01 \x01(\tR\x06reason\"\x15\n" + + "\x13SupervisorHeartbeat\"\x12\n" + + "\x10GatewayHeartbeat\"\xb7\x01\n" + + "\tRelayOpen\x12\x1d\n" + + "\n" + + "channel_id\x18\x01 \x01(\tR\tchannelId\x120\n" + + "\x03ssh\x18\x02 \x01(\v2\x1c.openshell.v1.SshRelayTargetH\x00R\x03ssh\x120\n" + + "\x03tcp\x18\x03 \x01(\v2\x1c.openshell.v1.TcpRelayTargetH\x00R\x03tcp\x12\x1d\n" + + "\n" + + "service_id\x18\x05 \x01(\tR\tserviceIdB\b\n" + + "\x06target\"\x10\n" + + "\x0eSshRelayTarget\"8\n" + + "\x0eTcpRelayTarget\x12\x12\n" + + "\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\x02 \x01(\rR\x04port\"*\n" + + "\tRelayInit\x12\x1d\n" + + "\n" + + "channel_id\x18\x01 \x01(\tR\tchannelId\"\\\n" + + "\n" + + "RelayFrame\x12-\n" + + "\x04init\x18\x01 \x01(\v2\x17.openshell.v1.RelayInitH\x00R\x04init\x12\x14\n" + + "\x04data\x18\x02 \x01(\fH\x00R\x04dataB\t\n" + + "\apayload\"`\n" + + "\x0fRelayOpenResult\x12\x1d\n" + + "\n" + + "channel_id\x18\x01 \x01(\tR\tchannelId\x12\x18\n" + + "\asuccess\x18\x02 \x01(\bR\asuccess\x12\x14\n" + + "\x05error\x18\x03 \x01(\tR\x05error\"C\n" + + "\n" + + "RelayClose\x12\x1d\n" + + "\n" + + "channel_id\x18\x01 \x01(\tR\tchannelId\x12\x16\n" + + "\x06reason\x18\x02 \x01(\tR\x06reason\"o\n" + + "\x0fL7RequestSample\x12\x16\n" + + "\x06method\x18\x01 \x01(\tR\x06method\x12\x12\n" + + "\x04path\x18\x02 \x01(\tR\x04path\x12\x1a\n" + + "\bdecision\x18\x03 \x01(\tR\bdecision\x12\x14\n" + + "\x05count\x18\x04 \x01(\rR\x05count\"\xe5\x04\n" + + "\rDenialSummary\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x12\n" + + "\x04host\x18\x02 \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\x03 \x01(\rR\x04port\x12\x16\n" + + "\x06binary\x18\x04 \x01(\tR\x06binary\x12\x1c\n" + + "\tancestors\x18\x05 \x03(\tR\tancestors\x12\x1f\n" + + "\vdeny_reason\x18\x06 \x01(\tR\n" + + "denyReason\x12\"\n" + + "\rfirst_seen_ms\x18\a \x01(\x03R\vfirstSeenMs\x12 \n" + + "\flast_seen_ms\x18\b \x01(\x03R\n" + + "lastSeenMs\x12\x14\n" + + "\x05count\x18\t \x01(\rR\x05count\x12)\n" + + "\x10suppressed_count\x18\n" + + " \x01(\rR\x0fsuppressedCount\x12\x1f\n" + + "\vtotal_count\x18\v \x01(\rR\n" + + "totalCount\x12'\n" + + "\x0fsample_cmdlines\x18\f \x03(\tR\x0esampleCmdlines\x12#\n" + + "\rbinary_sha256\x18\r \x01(\tR\fbinarySha256\x12\x1e\n" + + "\n" + + "persistent\x18\x0e \x01(\bR\n" + + "persistent\x12!\n" + + "\fdenial_stage\x18\x0f \x01(\tR\vdenialStage\x12K\n" + + "\x12l7_request_samples\x18\x10 \x03(\v2\x1d.openshell.v1.L7RequestSampleR\x10l7RequestSamples\x120\n" + + "\x14l7_inspection_active\x18\x11 \x01(\bR\x12l7InspectionActive\"T\n" + + "\x10DenialGroupCount\x12\x1d\n" + + "\n" + + "deny_group\x18\x01 \x01(\tR\tdenyGroup\x12!\n" + + "\fdenied_count\x18\x02 \x01(\rR\vdeniedCount\"\xc8\x01\n" + + "\x16NetworkActivitySummary\x124\n" + + "\x16network_activity_count\x18\x01 \x01(\rR\x14networkActivityCount\x12.\n" + + "\x13denied_action_count\x18\x02 \x01(\rR\x11deniedActionCount\x12H\n" + + "\x10denials_by_group\x18\x03 \x03(\v2\x1e.openshell.v1.DenialGroupCountR\x0edenialsByGroup\"\x94\x05\n" + + "\vPolicyChunk\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\x12\x1b\n" + + "\trule_name\x18\x03 \x01(\tR\bruleName\x12L\n" + + "\rproposed_rule\x18\x04 \x01(\v2'.openshell.sandbox.v1.NetworkPolicyRuleR\fproposedRule\x12\x1c\n" + + "\trationale\x18\x05 \x01(\tR\trationale\x12%\n" + + "\x0esecurity_notes\x18\x06 \x01(\tR\rsecurityNotes\x12\x1e\n" + + "\n" + + "confidence\x18\a \x01(\x02R\n" + + "confidence\x12,\n" + + "\x12denial_summary_ids\x18\b \x03(\tR\x10denialSummaryIds\x12\"\n" + + "\rcreated_at_ms\x18\t \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rdecided_at_ms\x18\n" + + " \x01(\x03R\vdecidedAtMs\x12\x14\n" + + "\x05stage\x18\v \x01(\tR\x05stage\x12.\n" + + "\x13supersedes_chunk_id\x18\f \x01(\tR\x11supersedesChunkId\x12\x1b\n" + + "\thit_count\x18\r \x01(\x05R\bhitCount\x12\"\n" + + "\rfirst_seen_ms\x18\x0e \x01(\x03R\vfirstSeenMs\x12 \n" + + "\flast_seen_ms\x18\x0f \x01(\x03R\n" + + "lastSeenMs\x12\x16\n" + + "\x06binary\x18\x10 \x01(\tR\x06binary\x12+\n" + + "\x11validation_result\x18\x11 \x01(\tR\x10validationResult\x12)\n" + + "\x10rejection_reason\x18\x12 \x01(\tR\x0frejectionReason\"\x96\x01\n" + + "\x11DraftPolicyUpdate\x12#\n" + + "\rdraft_version\x18\x01 \x01(\x04R\fdraftVersion\x12\x1d\n" + + "\n" + + "new_chunks\x18\x02 \x01(\rR\tnewChunks\x12#\n" + + "\rtotal_pending\x18\x03 \x01(\rR\ftotalPending\x12\x18\n" + + "\asummary\x18\x04 \x01(\tR\asummary\"\xd7\x02\n" + + "\x1bSubmitPolicyAnalysisRequest\x129\n" + + "\tsummaries\x18\x01 \x03(\v2\x1b.openshell.v1.DenialSummaryR\tsummaries\x12B\n" + + "\x0fproposed_chunks\x18\x02 \x03(\v2\x19.openshell.v1.PolicyChunkR\x0eproposedChunks\x12#\n" + + "\ranalysis_mode\x18\x03 \x01(\tR\fanalysisMode\x12\x12\n" + + "\x04name\x18\x04 \x01(\tR\x04name\x12b\n" + + "\x1anetwork_activity_summaries\x18\x05 \x03(\v2$.openshell.v1.NetworkActivitySummaryR\x18networkActivitySummaries\x12\x1c\n" + + "\tworkspace\x18\x06 \x01(\tR\tworkspace\"\xcb\x01\n" + + "\x1cSubmitPolicyAnalysisResponse\x12'\n" + + "\x0faccepted_chunks\x18\x01 \x01(\rR\x0eacceptedChunks\x12'\n" + + "\x0frejected_chunks\x18\x02 \x01(\rR\x0erejectedChunks\x12+\n" + + "\x11rejection_reasons\x18\x03 \x03(\tR\x10rejectionReasons\x12,\n" + + "\x12accepted_chunk_ids\x18\x04 \x03(\tR\x10acceptedChunkIds\"n\n" + + "\x15GetDraftPolicyRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12#\n" + + "\rstatus_filter\x18\x02 \x01(\tR\fstatusFilter\x12\x1c\n" + + "\tworkspace\x18\x03 \x01(\tR\tworkspace\"\xc8\x01\n" + + "\x16GetDraftPolicyResponse\x121\n" + + "\x06chunks\x18\x01 \x03(\v2\x19.openshell.v1.PolicyChunkR\x06chunks\x12'\n" + + "\x0frolling_summary\x18\x02 \x01(\tR\x0erollingSummary\x12#\n" + + "\rdraft_version\x18\x03 \x01(\x04R\fdraftVersion\x12-\n" + + "\x13last_analyzed_at_ms\x18\x04 \x01(\x03R\x10lastAnalyzedAtMs\"g\n" + + "\x18ApproveDraftChunkRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x19\n" + + "\bchunk_id\x18\x02 \x01(\tR\achunkId\x12\x1c\n" + + "\tworkspace\x18\x03 \x01(\tR\tworkspace\"c\n" + + "\x19ApproveDraftChunkResponse\x12%\n" + + "\x0epolicy_version\x18\x01 \x01(\rR\rpolicyVersion\x12\x1f\n" + + "\vpolicy_hash\x18\x02 \x01(\tR\n" + + "policyHash\"~\n" + + "\x17RejectDraftChunkRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x19\n" + + "\bchunk_id\x18\x02 \x01(\tR\achunkId\x12\x16\n" + + "\x06reason\x18\x03 \x01(\tR\x06reason\x12\x1c\n" + + "\tworkspace\x18\x04 \x01(\tR\tworkspace\"\x1a\n" + + "\x18RejectDraftChunkResponse\"\x8a\x01\n" + + "\x1cApproveAllDraftChunksRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x128\n" + + "\x18include_security_flagged\x18\x02 \x01(\bR\x16includeSecurityFlagged\x12\x1c\n" + + "\tworkspace\x18\x03 \x01(\tR\tworkspace\"\xb7\x01\n" + + "\x1dApproveAllDraftChunksResponse\x12%\n" + + "\x0epolicy_version\x18\x01 \x01(\rR\rpolicyVersion\x12\x1f\n" + + "\vpolicy_hash\x18\x02 \x01(\tR\n" + + "policyHash\x12'\n" + + "\x0fchunks_approved\x18\x03 \x01(\rR\x0echunksApproved\x12%\n" + + "\x0echunks_skipped\x18\x04 \x01(\rR\rchunksSkipped\"\xb2\x01\n" + + "\x15EditDraftChunkRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x19\n" + + "\bchunk_id\x18\x02 \x01(\tR\achunkId\x12L\n" + + "\rproposed_rule\x18\x03 \x01(\v2'.openshell.sandbox.v1.NetworkPolicyRuleR\fproposedRule\x12\x1c\n" + + "\tworkspace\x18\x04 \x01(\tR\tworkspace\"\x18\n" + + "\x16EditDraftChunkResponse\"d\n" + + "\x15UndoDraftChunkRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x19\n" + + "\bchunk_id\x18\x02 \x01(\tR\achunkId\x12\x1c\n" + + "\tworkspace\x18\x03 \x01(\tR\tworkspace\"`\n" + + "\x16UndoDraftChunkResponse\x12%\n" + + "\x0epolicy_version\x18\x01 \x01(\rR\rpolicyVersion\x12\x1f\n" + + "\vpolicy_hash\x18\x02 \x01(\tR\n" + + "policyHash\"K\n" + + "\x17ClearDraftChunksRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"A\n" + + "\x18ClearDraftChunksResponse\x12%\n" + + "\x0echunks_cleared\x18\x01 \x01(\rR\rchunksCleared\"J\n" + + "\x16GetDraftHistoryRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"\x92\x01\n" + + "\x11DraftHistoryEntry\x12!\n" + + "\ftimestamp_ms\x18\x01 \x01(\x03R\vtimestampMs\x12\x1d\n" + + "\n" + + "event_type\x18\x02 \x01(\tR\teventType\x12 \n" + + "\vdescription\x18\x03 \x01(\tR\vdescription\x12\x19\n" + + "\bchunk_id\x18\x04 \x01(\tR\achunkId\"T\n" + + "\x17GetDraftHistoryResponse\x129\n" + + "\aentries\x18\x01 \x03(\v2\x1f.openshell.v1.DraftHistoryEntryR\aentries\"\xbd\x02\n" + + "\x15PolicyRevisionPayload\x12;\n" + + "\x06policy\x18\x01 \x01(\v2#.openshell.sandbox.v1.SandboxPolicyR\x06policy\x12\x12\n" + + "\x04hash\x18\x02 \x01(\tR\x04hash\x12\x1d\n" + + "\n" + + "load_error\x18\x03 \x01(\tR\tloadError\x12 \n" + + "\floaded_at_ms\x18\x04 \x01(\x03R\n" + + "loadedAtMs\x12S\n" + + "\n" + + "provenance\x18\x05 \x03(\v23.openshell.v1.PolicyRevisionPayload.ProvenanceEntryR\n" + + "provenance\x1a=\n" + + "\x0fProvenanceEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc4\x03\n" + + "\x11DraftChunkPayload\x12\x1b\n" + + "\trule_name\x18\x01 \x01(\tR\bruleName\x12L\n" + + "\rproposed_rule\x18\x02 \x01(\v2'.openshell.sandbox.v1.NetworkPolicyRuleR\fproposedRule\x12\x1c\n" + + "\trationale\x18\x03 \x01(\tR\trationale\x12%\n" + + "\x0esecurity_notes\x18\x04 \x01(\tR\rsecurityNotes\x12\x1e\n" + + "\n" + + "confidence\x18\x05 \x01(\x02R\n" + + "confidence\x12\"\n" + + "\rdecided_at_ms\x18\x06 \x01(\x03R\vdecidedAtMs\x12\x12\n" + + "\x04host\x18\a \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\b \x01(\x05R\x04port\x12\x16\n" + + "\x06binary\x18\t \x01(\tR\x06binary\x12#\n" + + "\rdraft_version\x18\n" + + " \x01(\x03R\fdraftVersion\x12+\n" + + "\x11validation_result\x18\v \x01(\tR\x10validationResult\x12)\n" + + "\x10rejection_reason\x18\f \x01(\tR\x0frejectionReason\"\xe1\x03\n" + + "\x14StoredPolicyRevision\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x02 \x01(\tR\tsandboxId\x12\x18\n" + + "\aversion\x18\x03 \x01(\x03R\aversion\x12%\n" + + "\x0epolicy_payload\x18\x04 \x01(\fR\rpolicyPayload\x12\x1f\n" + + "\vpolicy_hash\x18\x05 \x01(\tR\n" + + "policyHash\x12\x16\n" + + "\x06status\x18\x06 \x01(\tR\x06status\x12\"\n" + + "\n" + + "load_error\x18\a \x01(\tH\x00R\tloadError\x88\x01\x01\x12\"\n" + + "\rcreated_at_ms\x18\b \x01(\x03R\vcreatedAtMs\x12%\n" + + "\floaded_at_ms\x18\t \x01(\x03H\x01R\n" + + "loadedAtMs\x88\x01\x01\x12R\n" + + "\n" + + "provenance\x18\n" + + " \x03(\v22.openshell.v1.StoredPolicyRevision.ProvenanceEntryR\n" + + "provenance\x1a=\n" + + "\x0fProvenanceEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\r\n" + + "\v_load_errorB\x0f\n" + + "\r_loaded_at_ms\"\xff\x04\n" + + "\x10StoredDraftChunk\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x02 \x01(\tR\tsandboxId\x12#\n" + + "\rdraft_version\x18\x03 \x01(\x03R\fdraftVersion\x12\x16\n" + + "\x06status\x18\x04 \x01(\tR\x06status\x12\x1b\n" + + "\trule_name\x18\x05 \x01(\tR\bruleName\x12#\n" + + "\rproposed_rule\x18\x06 \x01(\fR\fproposedRule\x12\x1c\n" + + "\trationale\x18\a \x01(\tR\trationale\x12%\n" + + "\x0esecurity_notes\x18\b \x01(\tR\rsecurityNotes\x12\x1e\n" + + "\n" + + "confidence\x18\t \x01(\x01R\n" + + "confidence\x12\"\n" + + "\rcreated_at_ms\x18\n" + + " \x01(\x03R\vcreatedAtMs\x12'\n" + + "\rdecided_at_ms\x18\v \x01(\x03H\x00R\vdecidedAtMs\x88\x01\x01\x12\x12\n" + + "\x04host\x18\f \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\r \x01(\x05R\x04port\x12\x16\n" + + "\x06binary\x18\x0e \x01(\tR\x06binary\x12\x1b\n" + + "\thit_count\x18\x0f \x01(\x05R\bhitCount\x12\"\n" + + "\rfirst_seen_ms\x18\x10 \x01(\x03R\vfirstSeenMs\x12 \n" + + "\flast_seen_ms\x18\x11 \x01(\x03R\n" + + "lastSeenMs\x12+\n" + + "\x11validation_result\x18\x12 \x01(\tR\x10validationResult\x12)\n" + + "\x10rejection_reason\x18\x13 \x01(\tR\x0frejectionReasonB\x10\n" + + "\x0e_decided_at_ms\"\xb1\x01\n" + + "\x16CreateWorkspaceRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12H\n" + + "\x06labels\x18\x02 \x03(\v20.openshell.v1.CreateWorkspaceRequest.LabelsEntryR\x06labels\x1a9\n" + + "\vLabelsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"Z\n" + + "\x17CreateWorkspaceResponse\x12?\n" + + "\tworkspace\x18\x01 \x01(\v2!.openshell.datamodel.v1.WorkspaceR\tworkspace\")\n" + + "\x13GetWorkspaceRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"W\n" + + "\x14GetWorkspaceResponse\x12?\n" + + "\tworkspace\x18\x01 \x01(\v2!.openshell.datamodel.v1.WorkspaceR\tworkspace\"l\n" + + "\x15ListWorkspacesRequest\x12\x14\n" + + "\x05limit\x18\x01 \x01(\rR\x05limit\x12\x16\n" + + "\x06offset\x18\x02 \x01(\rR\x06offset\x12%\n" + + "\x0elabel_selector\x18\x03 \x01(\tR\rlabelSelector\"[\n" + + "\x16ListWorkspacesResponse\x12A\n" + + "\n" + + "workspaces\x18\x01 \x03(\v2!.openshell.datamodel.v1.WorkspaceR\n" + + "workspaces\",\n" + + "\x16DeleteWorkspaceRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"3\n" + + "\x17DeleteWorkspaceResponse\x12\x18\n" + + "\adeleted\x18\x01 \x01(\bR\adeleted\"\xaf\x01\n" + + "\x0fWorkspaceMember\x12>\n" + + "\bmetadata\x18\x01 \x01(\v2\".openshell.datamodel.v1.ObjectMetaR\bmetadata\x12+\n" + + "\x11principal_subject\x18\x02 \x01(\tR\x10principalSubject\x12/\n" + + "\x04role\x18\x03 \x01(\x0e2\x1b.openshell.v1.WorkspaceRoleR\x04role\"\x97\x01\n" + + "\x19AddWorkspaceMemberRequest\x12\x1c\n" + + "\tworkspace\x18\x01 \x01(\tR\tworkspace\x12+\n" + + "\x11principal_subject\x18\x02 \x01(\tR\x10principalSubject\x12/\n" + + "\x04role\x18\x03 \x01(\x0e2\x1b.openshell.v1.WorkspaceRoleR\x04role\"S\n" + + "\x1aAddWorkspaceMemberResponse\x125\n" + + "\x06member\x18\x01 \x01(\v2\x1d.openshell.v1.WorkspaceMemberR\x06member\"i\n" + + "\x1cRemoveWorkspaceMemberRequest\x12\x1c\n" + + "\tworkspace\x18\x01 \x01(\tR\tworkspace\x12+\n" + + "\x11principal_subject\x18\x02 \x01(\tR\x10principalSubject\"9\n" + + "\x1dRemoveWorkspaceMemberResponse\x12\x18\n" + + "\aremoved\x18\x01 \x01(\bR\aremoved\"i\n" + + "\x1bListWorkspaceMembersRequest\x12\x1c\n" + + "\tworkspace\x18\x01 \x01(\tR\tworkspace\x12\x14\n" + + "\x05limit\x18\x02 \x01(\rR\x05limit\x12\x16\n" + + "\x06offset\x18\x03 \x01(\rR\x06offset\"W\n" + + "\x1cListWorkspaceMembersResponse\x127\n" + + "\amembers\x18\x01 \x03(\v2\x1d.openshell.v1.WorkspaceMemberR\amembers*\xb6\x01\n" + + "\fSandboxPhase\x12\x1d\n" + + "\x19SANDBOX_PHASE_UNSPECIFIED\x10\x00\x12\x1e\n" + + "\x1aSANDBOX_PHASE_PROVISIONING\x10\x01\x12\x17\n" + + "\x13SANDBOX_PHASE_READY\x10\x02\x12\x17\n" + + "\x13SANDBOX_PHASE_ERROR\x10\x03\x12\x1a\n" + + "\x16SANDBOX_PHASE_DELETING\x10\x04\x12\x19\n" + + "\x15SANDBOX_PHASE_UNKNOWN\x10\x05*\xc3\x03\n" + + "!ProviderCredentialRefreshStrategy\x124\n" + + "0PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED\x10\x00\x12/\n" + + "+PROVIDER_CREDENTIAL_REFRESH_STRATEGY_STATIC\x10\x01\x121\n" + + "-PROVIDER_CREDENTIAL_REFRESH_STRATEGY_EXTERNAL\x10\x02\x12=\n" + + "9PROVIDER_CREDENTIAL_REFRESH_STRATEGY_OAUTH2_REFRESH_TOKEN\x10\x03\x12B\n" + + ">PROVIDER_CREDENTIAL_REFRESH_STRATEGY_OAUTH2_CLIENT_CREDENTIALS\x10\x04\x12C\n" + + "?PROVIDER_CREDENTIAL_REFRESH_STRATEGY_GOOGLE_SERVICE_ACCOUNT_JWT\x10\x05\x12<\n" + + "8PROVIDER_CREDENTIAL_REFRESH_STRATEGY_AWS_STS_ASSUME_ROLE\x10\x06*\xdb\x02\n" + + "\x17ProviderProfileCategory\x12)\n" + + "%PROVIDER_PROFILE_CATEGORY_UNSPECIFIED\x10\x00\x12#\n" + + "\x1fPROVIDER_PROFILE_CATEGORY_OTHER\x10\x01\x12'\n" + + "#PROVIDER_PROFILE_CATEGORY_INFERENCE\x10\x02\x12#\n" + + "\x1fPROVIDER_PROFILE_CATEGORY_AGENT\x10\x03\x12,\n" + + "(PROVIDER_PROFILE_CATEGORY_SOURCE_CONTROL\x10\x04\x12'\n" + + "#PROVIDER_PROFILE_CATEGORY_MESSAGING\x10\x05\x12\"\n" + + "\x1ePROVIDER_PROFILE_CATEGORY_DATA\x10\x06\x12'\n" + + "#PROVIDER_PROFILE_CATEGORY_KNOWLEDGE\x10\a*\x9a\x01\n" + + "\fPolicyStatus\x12\x1d\n" + + "\x19POLICY_STATUS_UNSPECIFIED\x10\x00\x12\x19\n" + + "\x15POLICY_STATUS_PENDING\x10\x01\x12\x18\n" + + "\x14POLICY_STATUS_LOADED\x10\x02\x12\x18\n" + + "\x14POLICY_STATUS_FAILED\x10\x03\x12\x1c\n" + + "\x18POLICY_STATUS_SUPERSEDED\x10\x04*\x86\x01\n" + + "\rServiceStatus\x12\x1e\n" + + "\x1aSERVICE_STATUS_UNSPECIFIED\x10\x00\x12\x1a\n" + + "\x16SERVICE_STATUS_HEALTHY\x10\x01\x12\x1b\n" + + "\x17SERVICE_STATUS_DEGRADED\x10\x02\x12\x1c\n" + + "\x18SERVICE_STATUS_UNHEALTHY\x10\x03*b\n" + + "\rWorkspaceRole\x12\x1e\n" + + "\x1aWORKSPACE_ROLE_UNSPECIFIED\x10\x00\x12\x17\n" + + "\x13WORKSPACE_ROLE_USER\x10\x01\x12\x18\n" + + "\x14WORKSPACE_ROLE_ADMIN\x10\x022\xacB\n" + + "\tOpenShell\x12Z\n" + + "\x06Health\x12\x1b.openshell.v1.HealthRequest\x1a\x1c.openshell.v1.HealthResponse\"\x15\x82\xb5\x18\x11\n" + + "\x0funauthenticated\x12i\n" + + "\x0eGetCurrentUser\x12#.openshell.v1.GetCurrentUserRequest\x1a$.openshell.v1.GetCurrentUserResponse\"\f\x82\xb5\x18\b\n" + + "\x06bearer\x12\x86\x01\n" + + "\x0eGetGatewayInfo\x12#.openshell.v1.GetGatewayInfoRequest\x1a$.openshell.v1.GetGatewayInfoResponse\")\x82\xb5\x18%\n" + + "\x06bearer\x1a\x0eplatform_admin\"\vconfig:read\x12u\n" + + "\rCreateSandbox\x12\".openshell.v1.CreateSandboxRequest\x1a\x1d.openshell.v1.SandboxResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write\x12n\n" + + "\n" + + "GetSandbox\x12\x1f.openshell.v1.GetSandboxRequest\x1a\x1d.openshell.v1.SandboxResponse\" \x82\xb5\x18\x1c\n" + + "\x06bearer\x12\x04user\"\fsandbox:read\x12z\n" + + "\rListSandboxes\x12\".openshell.v1.ListSandboxesRequest\x1a#.openshell.v1.ListSandboxesResponse\" \x82\xb5\x18\x1c\n" + + "\x06bearer\x12\x04user\"\fsandbox:read\x12\x8f\x01\n" + + "\x14ListSandboxProviders\x12).openshell.v1.ListSandboxProvidersRequest\x1a*.openshell.v1.ListSandboxProvidersResponse\" \x82\xb5\x18\x1c\n" + + "\x06bearer\x12\x04user\"\fsandbox:read\x12\x93\x01\n" + + "\x15AttachSandboxProvider\x12*.openshell.v1.AttachSandboxProviderRequest\x1a+.openshell.v1.AttachSandboxProviderResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write\x12\x93\x01\n" + + "\x15DetachSandboxProvider\x12*.openshell.v1.DetachSandboxProviderRequest\x1a+.openshell.v1.DetachSandboxProviderResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write\x12{\n" + + "\rDeleteSandbox\x12\".openshell.v1.DeleteSandboxRequest\x1a#.openshell.v1.DeleteSandboxResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write\x12\x84\x01\n" + + "\x10CreateSshSession\x12%.openshell.v1.CreateSshSessionRequest\x1a&.openshell.v1.CreateSshSessionResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write\x12}\n" + + "\rExposeService\x12\".openshell.v1.ExposeServiceRequest\x1a%.openshell.v1.ServiceEndpointResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write\x12v\n" + + "\n" + + "GetService\x12\x1f.openshell.v1.GetServiceRequest\x1a%.openshell.v1.ServiceEndpointResponse\" \x82\xb5\x18\x1c\n" + + "\x06bearer\x12\x04user\"\fsandbox:read\x12w\n" + + "\fListServices\x12!.openshell.v1.ListServicesRequest\x1a\".openshell.v1.ListServicesResponse\" \x82\xb5\x18\x1c\n" + + "\x06bearer\x12\x04user\"\fsandbox:read\x12{\n" + + "\rDeleteService\x12\".openshell.v1.DeleteServiceRequest\x1a#.openshell.v1.DeleteServiceResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write\x12\x84\x01\n" + + "\x10RevokeSshSession\x12%.openshell.v1.RevokeSshSessionRequest\x1a&.openshell.v1.RevokeSshSessionResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write\x12t\n" + + "\vExecSandbox\x12 .openshell.v1.ExecSandboxRequest\x1a\x1e.openshell.v1.ExecSandboxEvent\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write0\x01\x12q\n" + + "\n" + + "ForwardTcp\x12\x1d.openshell.v1.TcpForwardFrame\x1a\x1d.openshell.v1.TcpForwardFrame\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write(\x010\x01\x12\x7f\n" + + "\x16ExecSandboxInteractive\x12\x1e.openshell.v1.ExecSandboxInput\x1a\x1e.openshell.v1.ExecSandboxEvent\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write(\x010\x01\x12z\n" + + "\x0eCreateProvider\x12#.openshell.v1.CreateProviderRequest\x1a\x1e.openshell.v1.ProviderResponse\"#\x82\xb5\x18\x1f\n" + + "\x06bearer\x12\x05admin\"\x0eprovider:write\x12r\n" + + "\vGetProvider\x12 .openshell.v1.GetProviderRequest\x1a\x1e.openshell.v1.ProviderResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rprovider:read\x12{\n" + + "\rListProviders\x12\".openshell.v1.ListProvidersRequest\x1a#.openshell.v1.ListProvidersResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rprovider:read\x12\x90\x01\n" + + "\x14ListProviderProfiles\x12).openshell.v1.ListProviderProfilesRequest\x1a*.openshell.v1.ListProviderProfilesResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rprovider:read\x12\x87\x01\n" + + "\x12GetProviderProfile\x12'.openshell.v1.GetProviderProfileRequest\x1a%.openshell.v1.ProviderProfileResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rprovider:read\x12\x98\x01\n" + + "\x16ImportProviderProfiles\x12+.openshell.v1.ImportProviderProfilesRequest\x1a,.openshell.v1.ImportProviderProfilesResponse\"#\x82\xb5\x18\x1f\n" + + "\x06bearer\x12\x05admin\"\x0eprovider:write\x12\x98\x01\n" + + "\x16UpdateProviderProfiles\x12+.openshell.v1.UpdateProviderProfilesRequest\x1a,.openshell.v1.UpdateProviderProfilesResponse\"#\x82\xb5\x18\x1f\n" + + "\x06bearer\x12\x05admin\"\x0eprovider:write\x12\x90\x01\n" + + "\x14LintProviderProfiles\x12).openshell.v1.LintProviderProfilesRequest\x1a*.openshell.v1.LintProviderProfilesResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rprovider:read\x12z\n" + + "\x0eUpdateProvider\x12#.openshell.v1.UpdateProviderRequest\x1a\x1e.openshell.v1.ProviderResponse\"#\x82\xb5\x18\x1f\n" + + "\x06bearer\x12\x05admin\"\x0eprovider:write\x12\x9c\x01\n" + + "\x18GetProviderRefreshStatus\x12-.openshell.v1.GetProviderRefreshStatusRequest\x1a..openshell.v1.GetProviderRefreshStatusResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rprovider:read\x12\x9e\x01\n" + + "\x18ConfigureProviderRefresh\x12-.openshell.v1.ConfigureProviderRefreshRequest\x1a..openshell.v1.ConfigureProviderRefreshResponse\"#\x82\xb5\x18\x1f\n" + + "\x06bearer\x12\x05admin\"\x0eprovider:write\x12\x9e\x01\n" + + "\x18RotateProviderCredential\x12-.openshell.v1.RotateProviderCredentialRequest\x1a..openshell.v1.RotateProviderCredentialResponse\"#\x82\xb5\x18\x1f\n" + + "\x06bearer\x12\x05admin\"\x0eprovider:write\x12\x95\x01\n" + + "\x15DeleteProviderRefresh\x12*.openshell.v1.DeleteProviderRefreshRequest\x1a+.openshell.v1.DeleteProviderRefreshResponse\"#\x82\xb5\x18\x1f\n" + + "\x06bearer\x12\x05admin\"\x0eprovider:write\x12\x80\x01\n" + + "\x0eDeleteProvider\x12#.openshell.v1.DeleteProviderRequest\x1a$.openshell.v1.DeleteProviderResponse\"#\x82\xb5\x18\x1f\n" + + "\x06bearer\x12\x05admin\"\x0eprovider:write\x12\x95\x01\n" + + "\x15DeleteProviderProfile\x12*.openshell.v1.DeleteProviderProfileRequest\x1a+.openshell.v1.DeleteProviderProfileResponse\"#\x82\xb5\x18\x1f\n" + + "\x06bearer\x12\x05admin\"\x0eprovider:write\x12\x90\x01\n" + + "\x10GetSandboxConfig\x12-.openshell.sandbox.v1.GetSandboxConfigRequest\x1a..openshell.sandbox.v1.GetSandboxConfigResponse\"\x1d\x82\xb5\x18\x19\n" + + "\x04dual\x12\x04user\"\vconfig:read\x12\x8c\x01\n" + + "\x10GetGatewayConfig\x12-.openshell.sandbox.v1.GetGatewayConfigRequest\x1a..openshell.sandbox.v1.GetGatewayConfigResponse\"\x19\x82\xb5\x18\x15\n" + + "\x06bearer\"\vconfig:read\x12v\n" + + "\fUpdateConfig\x12!.openshell.v1.UpdateConfigRequest\x1a\".openshell.v1.UpdateConfigResponse\"\x1f\x82\xb5\x18\x1b\n" + + "\x04dual\x12\x05admin\"\fconfig:write\x12\x95\x01\n" + + "\x16GetSandboxPolicyStatus\x12+.openshell.v1.GetSandboxPolicyStatusRequest\x1a,.openshell.v1.GetSandboxPolicyStatusResponse\" \x82\xb5\x18\x1c\n" + + "\x06bearer\x12\x04user\"\fsandbox:read\x12\x8c\x01\n" + + "\x13ListSandboxPolicies\x12(.openshell.v1.ListSandboxPoliciesRequest\x1a).openshell.v1.ListSandboxPoliciesResponse\" \x82\xb5\x18\x1c\n" + + "\x06bearer\x12\x04user\"\fsandbox:read\x12v\n" + + "\x12ReportPolicyStatus\x12'.openshell.v1.ReportPolicyStatusRequest\x1a(.openshell.v1.ReportPolicyStatusResponse\"\r\x82\xb5\x18\t\n" + + "\asandbox\x12\x97\x01\n" + + "\x1dGetSandboxProviderEnvironment\x122.openshell.v1.GetSandboxProviderEnvironmentRequest\x1a3.openshell.v1.GetSandboxProviderEnvironmentResponse\"\r\x82\xb5\x18\t\n" + + "\asandbox\x12}\n" + + "\x0eGetSandboxLogs\x12#.openshell.v1.GetSandboxLogsRequest\x1a$.openshell.v1.GetSandboxLogsResponse\" \x82\xb5\x18\x1c\n" + + "\x06bearer\x12\x04user\"\fsandbox:read\x12o\n" + + "\x0fPushSandboxLogs\x12$.openshell.v1.PushSandboxLogsRequest\x1a%.openshell.v1.PushSandboxLogsResponse\"\r\x82\xb5\x18\t\n" + + "\asandbox(\x01\x12e\n" + + "\x11ConnectSupervisor\x12\x1f.openshell.v1.SupervisorMessage\x1a\x1c.openshell.v1.GatewayMessage\"\r\x82\xb5\x18\t\n" + + "\asandbox(\x010\x01\x12T\n" + + "\vRelayStream\x12\x18.openshell.v1.RelayFrame\x1a\x18.openshell.v1.RelayFrame\"\r\x82\xb5\x18\t\n" + + "\asandbox(\x010\x01\x12w\n" + + "\fWatchSandbox\x12!.openshell.v1.WatchSandboxRequest\x1a .openshell.v1.SandboxStreamEvent\" \x82\xb5\x18\x1c\n" + + "\x06bearer\x12\x04user\"\fsandbox:read0\x01\x12|\n" + + "\x14SubmitPolicyAnalysis\x12).openshell.v1.SubmitPolicyAnalysisRequest\x1a*.openshell.v1.SubmitPolicyAnalysisResponse\"\r\x82\xb5\x18\t\n" + + "\asandbox\x12z\n" + + "\x0eGetDraftPolicy\x12#.openshell.v1.GetDraftPolicyRequest\x1a$.openshell.v1.GetDraftPolicyResponse\"\x1d\x82\xb5\x18\x19\n" + + "\x04dual\x12\x04user\"\vconfig:read\x12\x87\x01\n" + + "\x11ApproveDraftChunk\x12&.openshell.v1.ApproveDraftChunkRequest\x1a'.openshell.v1.ApproveDraftChunkResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x05admin\"\fconfig:write\x12\x84\x01\n" + + "\x10RejectDraftChunk\x12%.openshell.v1.RejectDraftChunkRequest\x1a&.openshell.v1.RejectDraftChunkResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x05admin\"\fconfig:write\x12\x93\x01\n" + + "\x15ApproveAllDraftChunks\x12*.openshell.v1.ApproveAllDraftChunksRequest\x1a+.openshell.v1.ApproveAllDraftChunksResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x05admin\"\fconfig:write\x12~\n" + + "\x0eEditDraftChunk\x12#.openshell.v1.EditDraftChunkRequest\x1a$.openshell.v1.EditDraftChunkResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x05admin\"\fconfig:write\x12~\n" + + "\x0eUndoDraftChunk\x12#.openshell.v1.UndoDraftChunkRequest\x1a$.openshell.v1.UndoDraftChunkResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x05admin\"\fconfig:write\x12\x84\x01\n" + + "\x10ClearDraftChunks\x12%.openshell.v1.ClearDraftChunksRequest\x1a&.openshell.v1.ClearDraftChunksResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x05admin\"\fconfig:write\x12\x7f\n" + + "\x0fGetDraftHistory\x12$.openshell.v1.GetDraftHistoryRequest\x1a%.openshell.v1.GetDraftHistoryResponse\"\x1f\x82\xb5\x18\x1b\n" + + "\x06bearer\x12\x04user\"\vconfig:read\x12s\n" + + "\x11IssueSandboxToken\x12&.openshell.v1.IssueSandboxTokenRequest\x1a'.openshell.v1.IssueSandboxTokenResponse\"\r\x82\xb5\x18\t\n" + + "\asandbox\x12y\n" + + "\x13RefreshSandboxToken\x12(.openshell.v1.RefreshSandboxTokenRequest\x1a).openshell.v1.RefreshSandboxTokenResponse\"\r\x82\xb5\x18\t\n" + + "\asandbox\x12\x8d\x01\n" + + "\x0fCreateWorkspace\x12$.openshell.v1.CreateWorkspaceRequest\x1a%.openshell.v1.CreateWorkspaceResponse\"-\x82\xb5\x18)\n" + + "\x06bearer\x1a\x0eplatform_admin\"\x0fworkspace:write\x12y\n" + + "\fGetWorkspace\x12!.openshell.v1.GetWorkspaceRequest\x1a\".openshell.v1.GetWorkspaceResponse\"\"\x82\xb5\x18\x1e\n" + + "\x06bearer\x12\x04user\"\x0eworkspace:read\x12\x7f\n" + + "\x0eListWorkspaces\x12#.openshell.v1.ListWorkspacesRequest\x1a$.openshell.v1.ListWorkspacesResponse\"\"\x82\xb5\x18\x1e\n" + + "\x06bearer\x12\x04user\"\x0eworkspace:read\x12\x8d\x01\n" + + "\x0fDeleteWorkspace\x12$.openshell.v1.DeleteWorkspaceRequest\x1a%.openshell.v1.DeleteWorkspaceResponse\"-\x82\xb5\x18)\n" + + "\x06bearer\x1a\x0eplatform_admin\"\x0fworkspace:write\x12\x8d\x01\n" + + "\x12AddWorkspaceMember\x12'.openshell.v1.AddWorkspaceMemberRequest\x1a(.openshell.v1.AddWorkspaceMemberResponse\"$\x82\xb5\x18 \n" + + "\x06bearer\x12\x05admin\"\x0fworkspace:write\x12\x96\x01\n" + + "\x15RemoveWorkspaceMember\x12*.openshell.v1.RemoveWorkspaceMemberRequest\x1a+.openshell.v1.RemoveWorkspaceMemberResponse\"$\x82\xb5\x18 \n" + + "\x06bearer\x12\x05admin\"\x0fworkspace:write\x12\x91\x01\n" + + "\x14ListWorkspaceMembers\x12).openshell.v1.ListWorkspaceMembersRequest\x1a*.openshell.v1.ListWorkspaceMembersResponse\"\"\x82\xb5\x18\x1e\n" + + "\x06bearer\x12\x04user\"\x0eworkspace:readb\x06proto3" + +var ( + file_openshell_proto_rawDescOnce sync.Once + file_openshell_proto_rawDescData []byte +) + +func file_openshell_proto_rawDescGZIP() []byte { + file_openshell_proto_rawDescOnce.Do(func() { + file_openshell_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_openshell_proto_rawDesc), len(file_openshell_proto_rawDesc))) + }) + return file_openshell_proto_rawDescData +} + +var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 6) +var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 203) +var file_openshell_proto_goTypes = []any{ + (SandboxPhase)(0), // 0: openshell.v1.SandboxPhase + (ProviderCredentialRefreshStrategy)(0), // 1: openshell.v1.ProviderCredentialRefreshStrategy + (ProviderProfileCategory)(0), // 2: openshell.v1.ProviderProfileCategory + (PolicyStatus)(0), // 3: openshell.v1.PolicyStatus + (ServiceStatus)(0), // 4: openshell.v1.ServiceStatus + (WorkspaceRole)(0), // 5: openshell.v1.WorkspaceRole + (*IssueSandboxTokenRequest)(nil), // 6: openshell.v1.IssueSandboxTokenRequest + (*IssueSandboxTokenResponse)(nil), // 7: openshell.v1.IssueSandboxTokenResponse + (*RefreshSandboxTokenRequest)(nil), // 8: openshell.v1.RefreshSandboxTokenRequest + (*RefreshSandboxTokenResponse)(nil), // 9: openshell.v1.RefreshSandboxTokenResponse + (*HealthRequest)(nil), // 10: openshell.v1.HealthRequest + (*HealthResponse)(nil), // 11: openshell.v1.HealthResponse + (*GetCurrentUserRequest)(nil), // 12: openshell.v1.GetCurrentUserRequest + (*GetCurrentUserResponse)(nil), // 13: openshell.v1.GetCurrentUserResponse + (*GetGatewayInfoRequest)(nil), // 14: openshell.v1.GetGatewayInfoRequest + (*GetGatewayInfoResponse)(nil), // 15: openshell.v1.GetGatewayInfoResponse + (*ComputeDriverInfo)(nil), // 16: openshell.v1.ComputeDriverInfo + (*ComputeDriverCapabilities)(nil), // 17: openshell.v1.ComputeDriverCapabilities + (*Sandbox)(nil), // 18: openshell.v1.Sandbox + (*SandboxSpec)(nil), // 19: openshell.v1.SandboxSpec + (*ResourceRequirements)(nil), // 20: openshell.v1.ResourceRequirements + (*GpuResourceRequirements)(nil), // 21: openshell.v1.GpuResourceRequirements + (*SandboxTemplate)(nil), // 22: openshell.v1.SandboxTemplate + (*SandboxStatus)(nil), // 23: openshell.v1.SandboxStatus + (*SandboxCondition)(nil), // 24: openshell.v1.SandboxCondition + (*PlatformEvent)(nil), // 25: openshell.v1.PlatformEvent + (*CreateSandboxRequest)(nil), // 26: openshell.v1.CreateSandboxRequest + (*GetSandboxRequest)(nil), // 27: openshell.v1.GetSandboxRequest + (*ListSandboxesRequest)(nil), // 28: openshell.v1.ListSandboxesRequest + (*ListSandboxProvidersRequest)(nil), // 29: openshell.v1.ListSandboxProvidersRequest + (*AttachSandboxProviderRequest)(nil), // 30: openshell.v1.AttachSandboxProviderRequest + (*DetachSandboxProviderRequest)(nil), // 31: openshell.v1.DetachSandboxProviderRequest + (*DeleteSandboxRequest)(nil), // 32: openshell.v1.DeleteSandboxRequest + (*SandboxResponse)(nil), // 33: openshell.v1.SandboxResponse + (*ListSandboxesResponse)(nil), // 34: openshell.v1.ListSandboxesResponse + (*ListSandboxProvidersResponse)(nil), // 35: openshell.v1.ListSandboxProvidersResponse + (*AttachSandboxProviderResponse)(nil), // 36: openshell.v1.AttachSandboxProviderResponse + (*DetachSandboxProviderResponse)(nil), // 37: openshell.v1.DetachSandboxProviderResponse + (*DeleteSandboxResponse)(nil), // 38: openshell.v1.DeleteSandboxResponse + (*CreateSshSessionRequest)(nil), // 39: openshell.v1.CreateSshSessionRequest + (*CreateSshSessionResponse)(nil), // 40: openshell.v1.CreateSshSessionResponse + (*ExposeServiceRequest)(nil), // 41: openshell.v1.ExposeServiceRequest + (*GetServiceRequest)(nil), // 42: openshell.v1.GetServiceRequest + (*ListServicesRequest)(nil), // 43: openshell.v1.ListServicesRequest + (*ListServicesResponse)(nil), // 44: openshell.v1.ListServicesResponse + (*DeleteServiceRequest)(nil), // 45: openshell.v1.DeleteServiceRequest + (*DeleteServiceResponse)(nil), // 46: openshell.v1.DeleteServiceResponse + (*ServiceEndpoint)(nil), // 47: openshell.v1.ServiceEndpoint + (*ServiceEndpointResponse)(nil), // 48: openshell.v1.ServiceEndpointResponse + (*RevokeSshSessionRequest)(nil), // 49: openshell.v1.RevokeSshSessionRequest + (*RevokeSshSessionResponse)(nil), // 50: openshell.v1.RevokeSshSessionResponse + (*ExecSandboxRequest)(nil), // 51: openshell.v1.ExecSandboxRequest + (*ExecSandboxStdout)(nil), // 52: openshell.v1.ExecSandboxStdout + (*ExecSandboxStderr)(nil), // 53: openshell.v1.ExecSandboxStderr + (*ExecSandboxExit)(nil), // 54: openshell.v1.ExecSandboxExit + (*ExecSandboxEvent)(nil), // 55: openshell.v1.ExecSandboxEvent + (*TcpForwardInit)(nil), // 56: openshell.v1.TcpForwardInit + (*TcpForwardFrame)(nil), // 57: openshell.v1.TcpForwardFrame + (*ExecSandboxInput)(nil), // 58: openshell.v1.ExecSandboxInput + (*ExecSandboxWindowResize)(nil), // 59: openshell.v1.ExecSandboxWindowResize + (*SshSession)(nil), // 60: openshell.v1.SshSession + (*WatchSandboxRequest)(nil), // 61: openshell.v1.WatchSandboxRequest + (*SandboxStreamEvent)(nil), // 62: openshell.v1.SandboxStreamEvent + (*SandboxLogLine)(nil), // 63: openshell.v1.SandboxLogLine + (*SandboxStreamWarning)(nil), // 64: openshell.v1.SandboxStreamWarning + (*CreateProviderRequest)(nil), // 65: openshell.v1.CreateProviderRequest + (*GetProviderRequest)(nil), // 66: openshell.v1.GetProviderRequest + (*ListProvidersRequest)(nil), // 67: openshell.v1.ListProvidersRequest + (*UpdateProviderRequest)(nil), // 68: openshell.v1.UpdateProviderRequest + (*DeleteProviderRequest)(nil), // 69: openshell.v1.DeleteProviderRequest + (*ProviderResponse)(nil), // 70: openshell.v1.ProviderResponse + (*ListProvidersResponse)(nil), // 71: openshell.v1.ListProvidersResponse + (*ListProviderProfilesRequest)(nil), // 72: openshell.v1.ListProviderProfilesRequest + (*GetProviderProfileRequest)(nil), // 73: openshell.v1.GetProviderProfileRequest + (*ProviderProfileImportItem)(nil), // 74: openshell.v1.ProviderProfileImportItem + (*ProviderProfileDiagnostic)(nil), // 75: openshell.v1.ProviderProfileDiagnostic + (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 76: openshell.v1.ProviderCredentialTokenGrantAudienceOverride + (*ProviderCredentialTokenGrant)(nil), // 77: openshell.v1.ProviderCredentialTokenGrant + (*ProviderProfileCredential)(nil), // 78: openshell.v1.ProviderProfileCredential + (*ProviderCredentialRefreshMaterial)(nil), // 79: openshell.v1.ProviderCredentialRefreshMaterial + (*ProviderCredentialRefreshOutput)(nil), // 80: openshell.v1.ProviderCredentialRefreshOutput + (*ProviderCredentialRefresh)(nil), // 81: openshell.v1.ProviderCredentialRefresh + (*ProviderCredentialRefreshStatus)(nil), // 82: openshell.v1.ProviderCredentialRefreshStatus + (*ProviderProfileDiscovery)(nil), // 83: openshell.v1.ProviderProfileDiscovery + (*StoredProviderCredentialRefreshState)(nil), // 84: openshell.v1.StoredProviderCredentialRefreshState + (*GetProviderRefreshStatusRequest)(nil), // 85: openshell.v1.GetProviderRefreshStatusRequest + (*GetProviderRefreshStatusResponse)(nil), // 86: openshell.v1.GetProviderRefreshStatusResponse + (*ConfigureProviderRefreshRequest)(nil), // 87: openshell.v1.ConfigureProviderRefreshRequest + (*ConfigureProviderRefreshResponse)(nil), // 88: openshell.v1.ConfigureProviderRefreshResponse + (*RotateProviderCredentialRequest)(nil), // 89: openshell.v1.RotateProviderCredentialRequest + (*RotateProviderCredentialResponse)(nil), // 90: openshell.v1.RotateProviderCredentialResponse + (*DeleteProviderRefreshRequest)(nil), // 91: openshell.v1.DeleteProviderRefreshRequest + (*DeleteProviderRefreshResponse)(nil), // 92: openshell.v1.DeleteProviderRefreshResponse + (*ProviderProfile)(nil), // 93: openshell.v1.ProviderProfile + (*StoredProviderProfile)(nil), // 94: openshell.v1.StoredProviderProfile + (*ProviderProfileResponse)(nil), // 95: openshell.v1.ProviderProfileResponse + (*ListProviderProfilesResponse)(nil), // 96: openshell.v1.ListProviderProfilesResponse + (*ImportProviderProfilesRequest)(nil), // 97: openshell.v1.ImportProviderProfilesRequest + (*ImportProviderProfilesResponse)(nil), // 98: openshell.v1.ImportProviderProfilesResponse + (*UpdateProviderProfilesRequest)(nil), // 99: openshell.v1.UpdateProviderProfilesRequest + (*UpdateProviderProfilesResponse)(nil), // 100: openshell.v1.UpdateProviderProfilesResponse + (*LintProviderProfilesRequest)(nil), // 101: openshell.v1.LintProviderProfilesRequest + (*LintProviderProfilesResponse)(nil), // 102: openshell.v1.LintProviderProfilesResponse + (*DeleteProviderResponse)(nil), // 103: openshell.v1.DeleteProviderResponse + (*DeleteProviderProfileRequest)(nil), // 104: openshell.v1.DeleteProviderProfileRequest + (*DeleteProviderProfileResponse)(nil), // 105: openshell.v1.DeleteProviderProfileResponse + (*GetSandboxProviderEnvironmentRequest)(nil), // 106: openshell.v1.GetSandboxProviderEnvironmentRequest + (*GetSandboxProviderEnvironmentResponse)(nil), // 107: openshell.v1.GetSandboxProviderEnvironmentResponse + (*UpdateConfigRequest)(nil), // 108: openshell.v1.UpdateConfigRequest + (*PolicyMergeOperation)(nil), // 109: openshell.v1.PolicyMergeOperation + (*AddNetworkRule)(nil), // 110: openshell.v1.AddNetworkRule + (*RemoveNetworkEndpoint)(nil), // 111: openshell.v1.RemoveNetworkEndpoint + (*RemoveNetworkRule)(nil), // 112: openshell.v1.RemoveNetworkRule + (*AddDenyRules)(nil), // 113: openshell.v1.AddDenyRules + (*AddAllowRules)(nil), // 114: openshell.v1.AddAllowRules + (*RemoveNetworkBinary)(nil), // 115: openshell.v1.RemoveNetworkBinary + (*UpdateConfigResponse)(nil), // 116: openshell.v1.UpdateConfigResponse + (*GetSandboxPolicyStatusRequest)(nil), // 117: openshell.v1.GetSandboxPolicyStatusRequest + (*GetSandboxPolicyStatusResponse)(nil), // 118: openshell.v1.GetSandboxPolicyStatusResponse + (*ListSandboxPoliciesRequest)(nil), // 119: openshell.v1.ListSandboxPoliciesRequest + (*ListSandboxPoliciesResponse)(nil), // 120: openshell.v1.ListSandboxPoliciesResponse + (*ReportPolicyStatusRequest)(nil), // 121: openshell.v1.ReportPolicyStatusRequest + (*ReportPolicyStatusResponse)(nil), // 122: openshell.v1.ReportPolicyStatusResponse + (*SandboxPolicyRevision)(nil), // 123: openshell.v1.SandboxPolicyRevision + (*GetSandboxLogsRequest)(nil), // 124: openshell.v1.GetSandboxLogsRequest + (*PushSandboxLogsRequest)(nil), // 125: openshell.v1.PushSandboxLogsRequest + (*PushSandboxLogsResponse)(nil), // 126: openshell.v1.PushSandboxLogsResponse + (*GetSandboxLogsResponse)(nil), // 127: openshell.v1.GetSandboxLogsResponse + (*SupervisorMessage)(nil), // 128: openshell.v1.SupervisorMessage + (*GatewayMessage)(nil), // 129: openshell.v1.GatewayMessage + (*SupervisorHello)(nil), // 130: openshell.v1.SupervisorHello + (*SessionAccepted)(nil), // 131: openshell.v1.SessionAccepted + (*SessionRejected)(nil), // 132: openshell.v1.SessionRejected + (*SupervisorHeartbeat)(nil), // 133: openshell.v1.SupervisorHeartbeat + (*GatewayHeartbeat)(nil), // 134: openshell.v1.GatewayHeartbeat + (*RelayOpen)(nil), // 135: openshell.v1.RelayOpen + (*SshRelayTarget)(nil), // 136: openshell.v1.SshRelayTarget + (*TcpRelayTarget)(nil), // 137: openshell.v1.TcpRelayTarget + (*RelayInit)(nil), // 138: openshell.v1.RelayInit + (*RelayFrame)(nil), // 139: openshell.v1.RelayFrame + (*RelayOpenResult)(nil), // 140: openshell.v1.RelayOpenResult + (*RelayClose)(nil), // 141: openshell.v1.RelayClose + (*L7RequestSample)(nil), // 142: openshell.v1.L7RequestSample + (*DenialSummary)(nil), // 143: openshell.v1.DenialSummary + (*DenialGroupCount)(nil), // 144: openshell.v1.DenialGroupCount + (*NetworkActivitySummary)(nil), // 145: openshell.v1.NetworkActivitySummary + (*PolicyChunk)(nil), // 146: openshell.v1.PolicyChunk + (*DraftPolicyUpdate)(nil), // 147: openshell.v1.DraftPolicyUpdate + (*SubmitPolicyAnalysisRequest)(nil), // 148: openshell.v1.SubmitPolicyAnalysisRequest + (*SubmitPolicyAnalysisResponse)(nil), // 149: openshell.v1.SubmitPolicyAnalysisResponse + (*GetDraftPolicyRequest)(nil), // 150: openshell.v1.GetDraftPolicyRequest + (*GetDraftPolicyResponse)(nil), // 151: openshell.v1.GetDraftPolicyResponse + (*ApproveDraftChunkRequest)(nil), // 152: openshell.v1.ApproveDraftChunkRequest + (*ApproveDraftChunkResponse)(nil), // 153: openshell.v1.ApproveDraftChunkResponse + (*RejectDraftChunkRequest)(nil), // 154: openshell.v1.RejectDraftChunkRequest + (*RejectDraftChunkResponse)(nil), // 155: openshell.v1.RejectDraftChunkResponse + (*ApproveAllDraftChunksRequest)(nil), // 156: openshell.v1.ApproveAllDraftChunksRequest + (*ApproveAllDraftChunksResponse)(nil), // 157: openshell.v1.ApproveAllDraftChunksResponse + (*EditDraftChunkRequest)(nil), // 158: openshell.v1.EditDraftChunkRequest + (*EditDraftChunkResponse)(nil), // 159: openshell.v1.EditDraftChunkResponse + (*UndoDraftChunkRequest)(nil), // 160: openshell.v1.UndoDraftChunkRequest + (*UndoDraftChunkResponse)(nil), // 161: openshell.v1.UndoDraftChunkResponse + (*ClearDraftChunksRequest)(nil), // 162: openshell.v1.ClearDraftChunksRequest + (*ClearDraftChunksResponse)(nil), // 163: openshell.v1.ClearDraftChunksResponse + (*GetDraftHistoryRequest)(nil), // 164: openshell.v1.GetDraftHistoryRequest + (*DraftHistoryEntry)(nil), // 165: openshell.v1.DraftHistoryEntry + (*GetDraftHistoryResponse)(nil), // 166: openshell.v1.GetDraftHistoryResponse + (*PolicyRevisionPayload)(nil), // 167: openshell.v1.PolicyRevisionPayload + (*DraftChunkPayload)(nil), // 168: openshell.v1.DraftChunkPayload + (*StoredPolicyRevision)(nil), // 169: openshell.v1.StoredPolicyRevision + (*StoredDraftChunk)(nil), // 170: openshell.v1.StoredDraftChunk + (*CreateWorkspaceRequest)(nil), // 171: openshell.v1.CreateWorkspaceRequest + (*CreateWorkspaceResponse)(nil), // 172: openshell.v1.CreateWorkspaceResponse + (*GetWorkspaceRequest)(nil), // 173: openshell.v1.GetWorkspaceRequest + (*GetWorkspaceResponse)(nil), // 174: openshell.v1.GetWorkspaceResponse + (*ListWorkspacesRequest)(nil), // 175: openshell.v1.ListWorkspacesRequest + (*ListWorkspacesResponse)(nil), // 176: openshell.v1.ListWorkspacesResponse + (*DeleteWorkspaceRequest)(nil), // 177: openshell.v1.DeleteWorkspaceRequest + (*DeleteWorkspaceResponse)(nil), // 178: openshell.v1.DeleteWorkspaceResponse + (*WorkspaceMember)(nil), // 179: openshell.v1.WorkspaceMember + (*AddWorkspaceMemberRequest)(nil), // 180: openshell.v1.AddWorkspaceMemberRequest + (*AddWorkspaceMemberResponse)(nil), // 181: openshell.v1.AddWorkspaceMemberResponse + (*RemoveWorkspaceMemberRequest)(nil), // 182: openshell.v1.RemoveWorkspaceMemberRequest + (*RemoveWorkspaceMemberResponse)(nil), // 183: openshell.v1.RemoveWorkspaceMemberResponse + (*ListWorkspaceMembersRequest)(nil), // 184: openshell.v1.ListWorkspaceMembersRequest + (*ListWorkspaceMembersResponse)(nil), // 185: openshell.v1.ListWorkspaceMembersResponse + nil, // 186: openshell.v1.SandboxSpec.EnvironmentEntry + nil, // 187: openshell.v1.SandboxTemplate.LabelsEntry + nil, // 188: openshell.v1.SandboxTemplate.AnnotationsEntry + nil, // 189: openshell.v1.SandboxTemplate.EnvironmentEntry + nil, // 190: openshell.v1.PlatformEvent.MetadataEntry + nil, // 191: openshell.v1.CreateSandboxRequest.LabelsEntry + nil, // 192: openshell.v1.CreateSandboxRequest.AnnotationsEntry + nil, // 193: openshell.v1.ExecSandboxRequest.EnvironmentEntry + nil, // 194: openshell.v1.SandboxLogLine.FieldsEntry + nil, // 195: openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry + nil, // 196: openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry + nil, // 197: openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry + nil, // 198: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + nil, // 199: openshell.v1.ProviderProfile.AnnotationsEntry + nil, // 200: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + nil, // 201: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry + nil, // 202: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + nil, // 203: openshell.v1.UpdateConfigRequest.AnnotationsEntry + nil, // 204: openshell.v1.UpdateConfigResponse.AnnotationsEntry + nil, // 205: openshell.v1.SandboxPolicyRevision.ProvenanceEntry + nil, // 206: openshell.v1.PolicyRevisionPayload.ProvenanceEntry + nil, // 207: openshell.v1.StoredPolicyRevision.ProvenanceEntry + nil, // 208: openshell.v1.CreateWorkspaceRequest.LabelsEntry + (*datamodelv1.ObjectMeta)(nil), // 209: openshell.datamodel.v1.ObjectMeta + (*sandboxv1.SandboxPolicy)(nil), // 210: openshell.sandbox.v1.SandboxPolicy + (*structpb.Struct)(nil), // 211: google.protobuf.Struct + (*datamodelv1.Provider)(nil), // 212: openshell.datamodel.v1.Provider + (*sandboxv1.NetworkEndpoint)(nil), // 213: openshell.sandbox.v1.NetworkEndpoint + (*sandboxv1.NetworkBinary)(nil), // 214: openshell.sandbox.v1.NetworkBinary + (*sandboxv1.SettingValue)(nil), // 215: openshell.sandbox.v1.SettingValue + (*sandboxv1.NetworkPolicyRule)(nil), // 216: openshell.sandbox.v1.NetworkPolicyRule + (*sandboxv1.L7DenyRule)(nil), // 217: openshell.sandbox.v1.L7DenyRule + (*sandboxv1.L7Rule)(nil), // 218: openshell.sandbox.v1.L7Rule + (*datamodelv1.Workspace)(nil), // 219: openshell.datamodel.v1.Workspace + (*sandboxv1.GetSandboxConfigRequest)(nil), // 220: openshell.sandbox.v1.GetSandboxConfigRequest + (*sandboxv1.GetGatewayConfigRequest)(nil), // 221: openshell.sandbox.v1.GetGatewayConfigRequest + (*sandboxv1.GetSandboxConfigResponse)(nil), // 222: openshell.sandbox.v1.GetSandboxConfigResponse + (*sandboxv1.GetGatewayConfigResponse)(nil), // 223: openshell.sandbox.v1.GetGatewayConfigResponse +} +var file_openshell_proto_depIdxs = []int32{ + 4, // 0: openshell.v1.HealthResponse.status:type_name -> openshell.v1.ServiceStatus + 4, // 1: openshell.v1.GetGatewayInfoResponse.status:type_name -> openshell.v1.ServiceStatus + 16, // 2: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo + 17, // 3: openshell.v1.ComputeDriverInfo.capabilities:type_name -> openshell.v1.ComputeDriverCapabilities + 209, // 4: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 19, // 5: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec + 23, // 6: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus + 186, // 7: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry + 22, // 8: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate + 210, // 9: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 20, // 10: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements + 21, // 11: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements + 187, // 12: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry + 188, // 13: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry + 189, // 14: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry + 211, // 15: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct + 211, // 16: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct + 24, // 17: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition + 0, // 18: openshell.v1.SandboxStatus.phase:type_name -> openshell.v1.SandboxPhase + 190, // 19: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry + 19, // 20: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec + 191, // 21: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry + 192, // 22: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry + 18, // 23: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox + 18, // 24: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox + 212, // 25: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 18, // 26: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 18, // 27: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 48, // 28: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse + 209, // 29: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 47, // 30: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint + 193, // 31: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry + 52, // 32: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout + 53, // 33: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr + 54, // 34: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit + 136, // 35: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget + 137, // 36: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget + 56, // 37: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit + 51, // 38: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest + 59, // 39: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize + 209, // 40: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 18, // 41: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox + 63, // 42: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine + 25, // 43: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent + 64, // 44: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning + 147, // 45: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate + 194, // 46: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry + 212, // 47: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 212, // 48: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 195, // 49: openshell.v1.UpdateProviderRequest.credential_expires_at_ms:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry + 212, // 50: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider + 212, // 51: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 93, // 52: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile + 76, // 53: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride + 81, // 54: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh + 77, // 55: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant + 1, // 56: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 79, // 57: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial + 80, // 58: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput + 1, // 59: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 209, // 60: openshell.v1.StoredProviderCredentialRefreshState.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 1, // 61: openshell.v1.StoredProviderCredentialRefreshState.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 196, // 62: openshell.v1.StoredProviderCredentialRefreshState.material:type_name -> openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry + 197, // 63: openshell.v1.StoredProviderCredentialRefreshState.additional_output_keys:type_name -> openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry + 82, // 64: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 1, // 65: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 198, // 66: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + 82, // 67: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 82, // 68: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 2, // 69: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory + 78, // 70: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential + 213, // 71: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 214, // 72: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 83, // 73: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery + 199, // 74: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry + 209, // 75: openshell.v1.StoredProviderProfile.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 93, // 76: openshell.v1.StoredProviderProfile.profile:type_name -> openshell.v1.ProviderProfile + 93, // 77: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile + 93, // 78: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 74, // 79: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 75, // 80: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 93, // 81: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 74, // 82: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem + 75, // 83: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 93, // 84: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile + 74, // 85: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 75, // 86: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 200, // 87: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + 201, // 88: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expires_at_ms:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry + 202, // 89: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + 210, // 90: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 215, // 91: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue + 109, // 92: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation + 203, // 93: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry + 110, // 94: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule + 111, // 95: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint + 112, // 96: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule + 113, // 97: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules + 114, // 98: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules + 115, // 99: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary + 216, // 100: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 217, // 101: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 218, // 102: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule + 204, // 103: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry + 123, // 104: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision + 123, // 105: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision + 3, // 106: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus + 3, // 107: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus + 210, // 108: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 205, // 109: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry + 63, // 110: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine + 63, // 111: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine + 130, // 112: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello + 133, // 113: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat + 140, // 114: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult + 141, // 115: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose + 131, // 116: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted + 132, // 117: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected + 134, // 118: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat + 135, // 119: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen + 141, // 120: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose + 136, // 121: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget + 137, // 122: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget + 138, // 123: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit + 142, // 124: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample + 144, // 125: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount + 216, // 126: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 143, // 127: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary + 146, // 128: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk + 145, // 129: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary + 146, // 130: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk + 216, // 131: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 165, // 132: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry + 210, // 133: openshell.v1.PolicyRevisionPayload.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 206, // 134: openshell.v1.PolicyRevisionPayload.provenance:type_name -> openshell.v1.PolicyRevisionPayload.ProvenanceEntry + 216, // 135: openshell.v1.DraftChunkPayload.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 207, // 136: openshell.v1.StoredPolicyRevision.provenance:type_name -> openshell.v1.StoredPolicyRevision.ProvenanceEntry + 208, // 137: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry + 219, // 138: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 219, // 139: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 219, // 140: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace + 209, // 141: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 5, // 142: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole + 5, // 143: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole + 179, // 144: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember + 179, // 145: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember + 78, // 146: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 10, // 147: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest + 12, // 148: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest + 14, // 149: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest + 26, // 150: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest + 27, // 151: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest + 28, // 152: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest + 29, // 153: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest + 30, // 154: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest + 31, // 155: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest + 32, // 156: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest + 39, // 157: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest + 41, // 158: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest + 42, // 159: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest + 43, // 160: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest + 45, // 161: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest + 49, // 162: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest + 51, // 163: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest + 57, // 164: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame + 58, // 165: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput + 65, // 166: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest + 66, // 167: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest + 67, // 168: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest + 72, // 169: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest + 73, // 170: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest + 97, // 171: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest + 99, // 172: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest + 101, // 173: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest + 68, // 174: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest + 85, // 175: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest + 87, // 176: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest + 89, // 177: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest + 91, // 178: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest + 69, // 179: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest + 104, // 180: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest + 220, // 181: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest + 221, // 182: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest + 108, // 183: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest + 117, // 184: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest + 119, // 185: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest + 121, // 186: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest + 106, // 187: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest + 124, // 188: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest + 125, // 189: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest + 128, // 190: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage + 139, // 191: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame + 61, // 192: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest + 148, // 193: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest + 150, // 194: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest + 152, // 195: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest + 154, // 196: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest + 156, // 197: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest + 158, // 198: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest + 160, // 199: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest + 162, // 200: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest + 164, // 201: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest + 6, // 202: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest + 8, // 203: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest + 171, // 204: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest + 173, // 205: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest + 175, // 206: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest + 177, // 207: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest + 180, // 208: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest + 182, // 209: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest + 184, // 210: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest + 11, // 211: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse + 13, // 212: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse + 15, // 213: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse + 33, // 214: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse + 33, // 215: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse + 34, // 216: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse + 35, // 217: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse + 36, // 218: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse + 37, // 219: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse + 38, // 220: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse + 40, // 221: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse + 48, // 222: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse + 48, // 223: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse + 44, // 224: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse + 46, // 225: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse + 50, // 226: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse + 55, // 227: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent + 57, // 228: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame + 55, // 229: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent + 70, // 230: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse + 70, // 231: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse + 71, // 232: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse + 96, // 233: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse + 95, // 234: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse + 98, // 235: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse + 100, // 236: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse + 102, // 237: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse + 70, // 238: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse + 86, // 239: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse + 88, // 240: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse + 90, // 241: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse + 92, // 242: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse + 103, // 243: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse + 105, // 244: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse + 222, // 245: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse + 223, // 246: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse + 116, // 247: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse + 118, // 248: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse + 120, // 249: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse + 122, // 250: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse + 107, // 251: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse + 127, // 252: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse + 126, // 253: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse + 129, // 254: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage + 139, // 255: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame + 62, // 256: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent + 149, // 257: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse + 151, // 258: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse + 153, // 259: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse + 155, // 260: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse + 157, // 261: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse + 159, // 262: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse + 161, // 263: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse + 163, // 264: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse + 166, // 265: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse + 7, // 266: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse + 9, // 267: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse + 172, // 268: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse + 174, // 269: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse + 176, // 270: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse + 178, // 271: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse + 181, // 272: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse + 183, // 273: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse + 185, // 274: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse + 211, // [211:275] is the sub-list for method output_type + 147, // [147:211] is the sub-list for method input_type + 147, // [147:147] is the sub-list for extension type_name + 147, // [147:147] is the sub-list for extension extendee + 0, // [0:147] is the sub-list for field type_name +} + +func init() { file_openshell_proto_init() } +func file_openshell_proto_init() { + if File_openshell_proto != nil { + return + } + file_openshell_proto_msgTypes[15].OneofWrappers = []any{} + file_openshell_proto_msgTypes[16].OneofWrappers = []any{} + file_openshell_proto_msgTypes[49].OneofWrappers = []any{ + (*ExecSandboxEvent_Stdout)(nil), + (*ExecSandboxEvent_Stderr)(nil), + (*ExecSandboxEvent_Exit)(nil), + } + file_openshell_proto_msgTypes[50].OneofWrappers = []any{ + (*TcpForwardInit_Ssh)(nil), + (*TcpForwardInit_Tcp)(nil), + } + file_openshell_proto_msgTypes[51].OneofWrappers = []any{ + (*TcpForwardFrame_Init)(nil), + (*TcpForwardFrame_Data)(nil), + } + file_openshell_proto_msgTypes[52].OneofWrappers = []any{ + (*ExecSandboxInput_Start)(nil), + (*ExecSandboxInput_Stdin)(nil), + (*ExecSandboxInput_Resize)(nil), + } + file_openshell_proto_msgTypes[56].OneofWrappers = []any{ + (*SandboxStreamEvent_Sandbox)(nil), + (*SandboxStreamEvent_Log)(nil), + (*SandboxStreamEvent_Event)(nil), + (*SandboxStreamEvent_Warning)(nil), + (*SandboxStreamEvent_DraftPolicyUpdate)(nil), + } + file_openshell_proto_msgTypes[81].OneofWrappers = []any{} + file_openshell_proto_msgTypes[103].OneofWrappers = []any{ + (*PolicyMergeOperation_AddRule)(nil), + (*PolicyMergeOperation_RemoveEndpoint)(nil), + (*PolicyMergeOperation_RemoveRule)(nil), + (*PolicyMergeOperation_AddDenyRules)(nil), + (*PolicyMergeOperation_AddAllowRules)(nil), + (*PolicyMergeOperation_RemoveBinary)(nil), + } + file_openshell_proto_msgTypes[122].OneofWrappers = []any{ + (*SupervisorMessage_Hello)(nil), + (*SupervisorMessage_Heartbeat)(nil), + (*SupervisorMessage_RelayOpenResult)(nil), + (*SupervisorMessage_RelayClose)(nil), + } + file_openshell_proto_msgTypes[123].OneofWrappers = []any{ + (*GatewayMessage_SessionAccepted)(nil), + (*GatewayMessage_SessionRejected)(nil), + (*GatewayMessage_Heartbeat)(nil), + (*GatewayMessage_RelayOpen)(nil), + (*GatewayMessage_RelayClose)(nil), + } + file_openshell_proto_msgTypes[129].OneofWrappers = []any{ + (*RelayOpen_Ssh)(nil), + (*RelayOpen_Tcp)(nil), + } + file_openshell_proto_msgTypes[133].OneofWrappers = []any{ + (*RelayFrame_Init)(nil), + (*RelayFrame_Data)(nil), + } + file_openshell_proto_msgTypes[163].OneofWrappers = []any{} + file_openshell_proto_msgTypes[164].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_openshell_proto_rawDesc), len(file_openshell_proto_rawDesc)), + NumEnums: 6, + NumMessages: 203, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_openshell_proto_goTypes, + DependencyIndexes: file_openshell_proto_depIdxs, + EnumInfos: file_openshell_proto_enumTypes, + MessageInfos: file_openshell_proto_msgTypes, + }.Build() + File_openshell_proto = out.File + file_openshell_proto_goTypes = nil + file_openshell_proto_depIdxs = nil +} diff --git a/sdk/go/proto/openshellv1/openshell_grpc.pb.go b/sdk/go/proto/openshellv1/openshell_grpc.pb.go new file mode 100644 index 000000000..40d625a39 --- /dev/null +++ b/sdk/go/proto/openshellv1/openshell_grpc.pb.go @@ -0,0 +1,2719 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc (unknown) +// source: openshell.proto + +package openshellv1 + +import ( + context "context" + sandboxv1 "github.com/NVIDIA/OpenShell/sdk/go/proto/sandboxv1" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + OpenShell_Health_FullMethodName = "/openshell.v1.OpenShell/Health" + OpenShell_GetCurrentUser_FullMethodName = "/openshell.v1.OpenShell/GetCurrentUser" + OpenShell_GetGatewayInfo_FullMethodName = "/openshell.v1.OpenShell/GetGatewayInfo" + OpenShell_CreateSandbox_FullMethodName = "/openshell.v1.OpenShell/CreateSandbox" + OpenShell_GetSandbox_FullMethodName = "/openshell.v1.OpenShell/GetSandbox" + OpenShell_ListSandboxes_FullMethodName = "/openshell.v1.OpenShell/ListSandboxes" + OpenShell_ListSandboxProviders_FullMethodName = "/openshell.v1.OpenShell/ListSandboxProviders" + OpenShell_AttachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/AttachSandboxProvider" + OpenShell_DetachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/DetachSandboxProvider" + OpenShell_DeleteSandbox_FullMethodName = "/openshell.v1.OpenShell/DeleteSandbox" + OpenShell_CreateSshSession_FullMethodName = "/openshell.v1.OpenShell/CreateSshSession" + OpenShell_ExposeService_FullMethodName = "/openshell.v1.OpenShell/ExposeService" + OpenShell_GetService_FullMethodName = "/openshell.v1.OpenShell/GetService" + OpenShell_ListServices_FullMethodName = "/openshell.v1.OpenShell/ListServices" + OpenShell_DeleteService_FullMethodName = "/openshell.v1.OpenShell/DeleteService" + OpenShell_RevokeSshSession_FullMethodName = "/openshell.v1.OpenShell/RevokeSshSession" + OpenShell_ExecSandbox_FullMethodName = "/openshell.v1.OpenShell/ExecSandbox" + OpenShell_ForwardTcp_FullMethodName = "/openshell.v1.OpenShell/ForwardTcp" + OpenShell_ExecSandboxInteractive_FullMethodName = "/openshell.v1.OpenShell/ExecSandboxInteractive" + OpenShell_CreateProvider_FullMethodName = "/openshell.v1.OpenShell/CreateProvider" + OpenShell_GetProvider_FullMethodName = "/openshell.v1.OpenShell/GetProvider" + OpenShell_ListProviders_FullMethodName = "/openshell.v1.OpenShell/ListProviders" + OpenShell_ListProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/ListProviderProfiles" + OpenShell_GetProviderProfile_FullMethodName = "/openshell.v1.OpenShell/GetProviderProfile" + OpenShell_ImportProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/ImportProviderProfiles" + OpenShell_UpdateProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/UpdateProviderProfiles" + OpenShell_LintProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/LintProviderProfiles" + OpenShell_UpdateProvider_FullMethodName = "/openshell.v1.OpenShell/UpdateProvider" + OpenShell_GetProviderRefreshStatus_FullMethodName = "/openshell.v1.OpenShell/GetProviderRefreshStatus" + OpenShell_ConfigureProviderRefresh_FullMethodName = "/openshell.v1.OpenShell/ConfigureProviderRefresh" + OpenShell_RotateProviderCredential_FullMethodName = "/openshell.v1.OpenShell/RotateProviderCredential" + OpenShell_DeleteProviderRefresh_FullMethodName = "/openshell.v1.OpenShell/DeleteProviderRefresh" + OpenShell_DeleteProvider_FullMethodName = "/openshell.v1.OpenShell/DeleteProvider" + OpenShell_DeleteProviderProfile_FullMethodName = "/openshell.v1.OpenShell/DeleteProviderProfile" + OpenShell_GetSandboxConfig_FullMethodName = "/openshell.v1.OpenShell/GetSandboxConfig" + OpenShell_GetGatewayConfig_FullMethodName = "/openshell.v1.OpenShell/GetGatewayConfig" + OpenShell_UpdateConfig_FullMethodName = "/openshell.v1.OpenShell/UpdateConfig" + OpenShell_GetSandboxPolicyStatus_FullMethodName = "/openshell.v1.OpenShell/GetSandboxPolicyStatus" + OpenShell_ListSandboxPolicies_FullMethodName = "/openshell.v1.OpenShell/ListSandboxPolicies" + OpenShell_ReportPolicyStatus_FullMethodName = "/openshell.v1.OpenShell/ReportPolicyStatus" + OpenShell_GetSandboxProviderEnvironment_FullMethodName = "/openshell.v1.OpenShell/GetSandboxProviderEnvironment" + OpenShell_GetSandboxLogs_FullMethodName = "/openshell.v1.OpenShell/GetSandboxLogs" + OpenShell_PushSandboxLogs_FullMethodName = "/openshell.v1.OpenShell/PushSandboxLogs" + OpenShell_ConnectSupervisor_FullMethodName = "/openshell.v1.OpenShell/ConnectSupervisor" + OpenShell_RelayStream_FullMethodName = "/openshell.v1.OpenShell/RelayStream" + OpenShell_WatchSandbox_FullMethodName = "/openshell.v1.OpenShell/WatchSandbox" + OpenShell_SubmitPolicyAnalysis_FullMethodName = "/openshell.v1.OpenShell/SubmitPolicyAnalysis" + OpenShell_GetDraftPolicy_FullMethodName = "/openshell.v1.OpenShell/GetDraftPolicy" + OpenShell_ApproveDraftChunk_FullMethodName = "/openshell.v1.OpenShell/ApproveDraftChunk" + OpenShell_RejectDraftChunk_FullMethodName = "/openshell.v1.OpenShell/RejectDraftChunk" + OpenShell_ApproveAllDraftChunks_FullMethodName = "/openshell.v1.OpenShell/ApproveAllDraftChunks" + OpenShell_EditDraftChunk_FullMethodName = "/openshell.v1.OpenShell/EditDraftChunk" + OpenShell_UndoDraftChunk_FullMethodName = "/openshell.v1.OpenShell/UndoDraftChunk" + OpenShell_ClearDraftChunks_FullMethodName = "/openshell.v1.OpenShell/ClearDraftChunks" + OpenShell_GetDraftHistory_FullMethodName = "/openshell.v1.OpenShell/GetDraftHistory" + OpenShell_IssueSandboxToken_FullMethodName = "/openshell.v1.OpenShell/IssueSandboxToken" + OpenShell_RefreshSandboxToken_FullMethodName = "/openshell.v1.OpenShell/RefreshSandboxToken" + OpenShell_CreateWorkspace_FullMethodName = "/openshell.v1.OpenShell/CreateWorkspace" + OpenShell_GetWorkspace_FullMethodName = "/openshell.v1.OpenShell/GetWorkspace" + OpenShell_ListWorkspaces_FullMethodName = "/openshell.v1.OpenShell/ListWorkspaces" + OpenShell_DeleteWorkspace_FullMethodName = "/openshell.v1.OpenShell/DeleteWorkspace" + OpenShell_AddWorkspaceMember_FullMethodName = "/openshell.v1.OpenShell/AddWorkspaceMember" + OpenShell_RemoveWorkspaceMember_FullMethodName = "/openshell.v1.OpenShell/RemoveWorkspaceMember" + OpenShell_ListWorkspaceMembers_FullMethodName = "/openshell.v1.OpenShell/ListWorkspaceMembers" +) + +// OpenShellClient is the client API for OpenShell service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// 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. +type OpenShellClient interface { + // Check the health of the service. + Health(ctx context.Context, in *HealthRequest, opts ...grpc.CallOption) (*HealthResponse, error) + // Return the authenticated caller identity established by the gateway. + GetCurrentUser(ctx context.Context, in *GetCurrentUserRequest, opts ...grpc.CallOption) (*GetCurrentUserResponse, error) + // Fetch elevated live gateway runtime metadata. + GetGatewayInfo(ctx context.Context, in *GetGatewayInfoRequest, opts ...grpc.CallOption) (*GetGatewayInfoResponse, error) + // Create a new sandbox. + CreateSandbox(ctx context.Context, in *CreateSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) + // Fetch a sandbox by name. + GetSandbox(ctx context.Context, in *GetSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) + // List sandboxes. + ListSandboxes(ctx context.Context, in *ListSandboxesRequest, opts ...grpc.CallOption) (*ListSandboxesResponse, error) + // List provider records attached to a sandbox. + ListSandboxProviders(ctx context.Context, in *ListSandboxProvidersRequest, opts ...grpc.CallOption) (*ListSandboxProvidersResponse, error) + // Attach a provider record to an existing sandbox. + AttachSandboxProvider(ctx context.Context, in *AttachSandboxProviderRequest, opts ...grpc.CallOption) (*AttachSandboxProviderResponse, error) + // Detach a provider record from an existing sandbox. + DetachSandboxProvider(ctx context.Context, in *DetachSandboxProviderRequest, opts ...grpc.CallOption) (*DetachSandboxProviderResponse, error) + // Delete a sandbox by name. + DeleteSandbox(ctx context.Context, in *DeleteSandboxRequest, opts ...grpc.CallOption) (*DeleteSandboxResponse, error) + // Create a short-lived SSH session for a sandbox. + CreateSshSession(ctx context.Context, in *CreateSshSessionRequest, opts ...grpc.CallOption) (*CreateSshSessionResponse, error) + // Create or update a sandbox HTTP service endpoint for local routing. + ExposeService(ctx context.Context, in *ExposeServiceRequest, opts ...grpc.CallOption) (*ServiceEndpointResponse, error) + // Fetch one sandbox HTTP service endpoint. + GetService(ctx context.Context, in *GetServiceRequest, opts ...grpc.CallOption) (*ServiceEndpointResponse, error) + // List sandbox HTTP service endpoints. + ListServices(ctx context.Context, in *ListServicesRequest, opts ...grpc.CallOption) (*ListServicesResponse, error) + // Delete one sandbox HTTP service endpoint. + DeleteService(ctx context.Context, in *DeleteServiceRequest, opts ...grpc.CallOption) (*DeleteServiceResponse, error) + // Revoke a previously issued SSH session. + RevokeSshSession(ctx context.Context, in *RevokeSshSessionRequest, opts ...grpc.CallOption) (*RevokeSshSessionResponse, error) + // Execute a command in a ready sandbox and stream output. + ExecSandbox(ctx context.Context, in *ExecSandboxRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ExecSandboxEvent], error) + // Forward one CLI-side TCP connection to a loopback TCP target in a sandbox. + ForwardTcp(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[TcpForwardFrame, TcpForwardFrame], error) + // 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. + ExecSandboxInteractive(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ExecSandboxInput, ExecSandboxEvent], error) + // Create a provider. + CreateProvider(ctx context.Context, in *CreateProviderRequest, opts ...grpc.CallOption) (*ProviderResponse, error) + // Fetch a provider by name. + GetProvider(ctx context.Context, in *GetProviderRequest, opts ...grpc.CallOption) (*ProviderResponse, error) + // List providers. + ListProviders(ctx context.Context, in *ListProvidersRequest, opts ...grpc.CallOption) (*ListProvidersResponse, error) + // List available provider type profiles. + ListProviderProfiles(ctx context.Context, in *ListProviderProfilesRequest, opts ...grpc.CallOption) (*ListProviderProfilesResponse, error) + // Fetch one provider type profile by id. + GetProviderProfile(ctx context.Context, in *GetProviderProfileRequest, opts ...grpc.CallOption) (*ProviderProfileResponse, error) + // Import custom provider type profiles. + ImportProviderProfiles(ctx context.Context, in *ImportProviderProfilesRequest, opts ...grpc.CallOption) (*ImportProviderProfilesResponse, error) + // Update an existing custom provider type profile. + UpdateProviderProfiles(ctx context.Context, in *UpdateProviderProfilesRequest, opts ...grpc.CallOption) (*UpdateProviderProfilesResponse, error) + // Validate provider type profiles without registering them. + LintProviderProfiles(ctx context.Context, in *LintProviderProfilesRequest, opts ...grpc.CallOption) (*LintProviderProfilesResponse, error) + // Update an existing provider by name. + UpdateProvider(ctx context.Context, in *UpdateProviderRequest, opts ...grpc.CallOption) (*ProviderResponse, error) + // Fetch refresh status for one provider or provider credential. + GetProviderRefreshStatus(ctx context.Context, in *GetProviderRefreshStatusRequest, opts ...grpc.CallOption) (*GetProviderRefreshStatusResponse, error) + // Configure gateway-owned refresh material for one provider credential. + ConfigureProviderRefresh(ctx context.Context, in *ConfigureProviderRefreshRequest, opts ...grpc.CallOption) (*ConfigureProviderRefreshResponse, error) + // Record a gateway-owned refresh request for one provider credential. + RotateProviderCredential(ctx context.Context, in *RotateProviderCredentialRequest, opts ...grpc.CallOption) (*RotateProviderCredentialResponse, error) + // Delete gateway-owned refresh configuration for one provider credential. + DeleteProviderRefresh(ctx context.Context, in *DeleteProviderRefreshRequest, opts ...grpc.CallOption) (*DeleteProviderRefreshResponse, error) + // Delete a provider by name. + DeleteProvider(ctx context.Context, in *DeleteProviderRequest, opts ...grpc.CallOption) (*DeleteProviderResponse, error) + // Delete a custom provider type profile by id. + DeleteProviderProfile(ctx context.Context, in *DeleteProviderProfileRequest, opts ...grpc.CallOption) (*DeleteProviderProfileResponse, error) + // Get sandbox settings by id (called by sandbox entrypoint and poll loop). + GetSandboxConfig(ctx context.Context, in *sandboxv1.GetSandboxConfigRequest, opts ...grpc.CallOption) (*sandboxv1.GetSandboxConfigResponse, error) + // Get gateway-global settings (read-only feature flags; any authenticated + // user may read these so the CLI and TUI can discover capabilities like + // providers_v2_enabled 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. + GetGatewayConfig(ctx context.Context, in *sandboxv1.GetGatewayConfigRequest, opts ...grpc.CallOption) (*sandboxv1.GetGatewayConfigResponse, error) + // Update settings or policy at sandbox or global scope. + UpdateConfig(ctx context.Context, in *UpdateConfigRequest, opts ...grpc.CallOption) (*UpdateConfigResponse, error) + // Get the load status of a specific policy version. + GetSandboxPolicyStatus(ctx context.Context, in *GetSandboxPolicyStatusRequest, opts ...grpc.CallOption) (*GetSandboxPolicyStatusResponse, error) + // List policy history for a sandbox. + ListSandboxPolicies(ctx context.Context, in *ListSandboxPoliciesRequest, opts ...grpc.CallOption) (*ListSandboxPoliciesResponse, error) + // Report policy load result (called by sandbox after reload attempt). + ReportPolicyStatus(ctx context.Context, in *ReportPolicyStatusRequest, opts ...grpc.CallOption) (*ReportPolicyStatusResponse, error) + // Get provider environment for a sandbox (called by sandbox supervisor at startup). + GetSandboxProviderEnvironment(ctx context.Context, in *GetSandboxProviderEnvironmentRequest, opts ...grpc.CallOption) (*GetSandboxProviderEnvironmentResponse, error) + // Fetch recent sandbox logs (one-shot). + GetSandboxLogs(ctx context.Context, in *GetSandboxLogsRequest, opts ...grpc.CallOption) (*GetSandboxLogsResponse, error) + // Push sandbox supervisor logs to the server (client-streaming). + PushSandboxLogs(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[PushSandboxLogsRequest, PushSandboxLogsResponse], error) + // 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. + ConnectSupervisor(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SupervisorMessage, GatewayMessage], error) + // 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. + RelayStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[RelayFrame, RelayFrame], error) + // 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 + WatchSandbox(ctx context.Context, in *WatchSandboxRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[SandboxStreamEvent], error) + // Submit denial analysis results from sandbox (summaries + proposed chunks). + SubmitPolicyAnalysis(ctx context.Context, in *SubmitPolicyAnalysisRequest, opts ...grpc.CallOption) (*SubmitPolicyAnalysisResponse, error) + // Get draft policy recommendations for a sandbox. + GetDraftPolicy(ctx context.Context, in *GetDraftPolicyRequest, opts ...grpc.CallOption) (*GetDraftPolicyResponse, error) + // Approve a single draft policy chunk (merges into active policy). + ApproveDraftChunk(ctx context.Context, in *ApproveDraftChunkRequest, opts ...grpc.CallOption) (*ApproveDraftChunkResponse, error) + // Reject a single draft policy chunk. + RejectDraftChunk(ctx context.Context, in *RejectDraftChunkRequest, opts ...grpc.CallOption) (*RejectDraftChunkResponse, error) + // Approve all pending draft chunks (skips security-flagged unless forced). + ApproveAllDraftChunks(ctx context.Context, in *ApproveAllDraftChunksRequest, opts ...grpc.CallOption) (*ApproveAllDraftChunksResponse, error) + // Edit a pending draft chunk in-place (e.g. narrow allowed_ips). + EditDraftChunk(ctx context.Context, in *EditDraftChunkRequest, opts ...grpc.CallOption) (*EditDraftChunkResponse, error) + // Reverse an approval (remove merged rule from active policy). + UndoDraftChunk(ctx context.Context, in *UndoDraftChunkRequest, opts ...grpc.CallOption) (*UndoDraftChunkResponse, error) + // Clear all pending draft chunks for a sandbox. + ClearDraftChunks(ctx context.Context, in *ClearDraftChunksRequest, opts ...grpc.CallOption) (*ClearDraftChunksResponse, error) + // Get decision history for a sandbox's draft policy. + GetDraftHistory(ctx context.Context, in *GetDraftHistoryRequest, opts ...grpc.CallOption) (*GetDraftHistoryResponse, error) + // 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. + IssueSandboxToken(ctx context.Context, in *IssueSandboxTokenRequest, opts ...grpc.CallOption) (*IssueSandboxTokenResponse, error) + // 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. + RefreshSandboxToken(ctx context.Context, in *RefreshSandboxTokenRequest, opts ...grpc.CallOption) (*RefreshSandboxTokenResponse, error) + // Create a workspace. + CreateWorkspace(ctx context.Context, in *CreateWorkspaceRequest, opts ...grpc.CallOption) (*CreateWorkspaceResponse, error) + // Fetch a workspace by name. + GetWorkspace(ctx context.Context, in *GetWorkspaceRequest, opts ...grpc.CallOption) (*GetWorkspaceResponse, error) + // List workspaces. + ListWorkspaces(ctx context.Context, in *ListWorkspacesRequest, opts ...grpc.CallOption) (*ListWorkspacesResponse, error) + // Delete a workspace by name. + DeleteWorkspace(ctx context.Context, in *DeleteWorkspaceRequest, opts ...grpc.CallOption) (*DeleteWorkspaceResponse, error) + // Add a member to a workspace. + AddWorkspaceMember(ctx context.Context, in *AddWorkspaceMemberRequest, opts ...grpc.CallOption) (*AddWorkspaceMemberResponse, error) + // Remove a member from a workspace. + RemoveWorkspaceMember(ctx context.Context, in *RemoveWorkspaceMemberRequest, opts ...grpc.CallOption) (*RemoveWorkspaceMemberResponse, error) + // List members of a workspace. + ListWorkspaceMembers(ctx context.Context, in *ListWorkspaceMembersRequest, opts ...grpc.CallOption) (*ListWorkspaceMembersResponse, error) +} + +type openShellClient struct { + cc grpc.ClientConnInterface +} + +func NewOpenShellClient(cc grpc.ClientConnInterface) OpenShellClient { + return &openShellClient{cc} +} + +func (c *openShellClient) Health(ctx context.Context, in *HealthRequest, opts ...grpc.CallOption) (*HealthResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(HealthResponse) + err := c.cc.Invoke(ctx, OpenShell_Health_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetCurrentUser(ctx context.Context, in *GetCurrentUserRequest, opts ...grpc.CallOption) (*GetCurrentUserResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetCurrentUserResponse) + err := c.cc.Invoke(ctx, OpenShell_GetCurrentUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetGatewayInfo(ctx context.Context, in *GetGatewayInfoRequest, opts ...grpc.CallOption) (*GetGatewayInfoResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetGatewayInfoResponse) + err := c.cc.Invoke(ctx, OpenShell_GetGatewayInfo_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) CreateSandbox(ctx context.Context, in *CreateSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SandboxResponse) + err := c.cc.Invoke(ctx, OpenShell_CreateSandbox_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetSandbox(ctx context.Context, in *GetSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SandboxResponse) + err := c.cc.Invoke(ctx, OpenShell_GetSandbox_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ListSandboxes(ctx context.Context, in *ListSandboxesRequest, opts ...grpc.CallOption) (*ListSandboxesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListSandboxesResponse) + err := c.cc.Invoke(ctx, OpenShell_ListSandboxes_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ListSandboxProviders(ctx context.Context, in *ListSandboxProvidersRequest, opts ...grpc.CallOption) (*ListSandboxProvidersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListSandboxProvidersResponse) + err := c.cc.Invoke(ctx, OpenShell_ListSandboxProviders_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) AttachSandboxProvider(ctx context.Context, in *AttachSandboxProviderRequest, opts ...grpc.CallOption) (*AttachSandboxProviderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AttachSandboxProviderResponse) + err := c.cc.Invoke(ctx, OpenShell_AttachSandboxProvider_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) DetachSandboxProvider(ctx context.Context, in *DetachSandboxProviderRequest, opts ...grpc.CallOption) (*DetachSandboxProviderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DetachSandboxProviderResponse) + err := c.cc.Invoke(ctx, OpenShell_DetachSandboxProvider_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) DeleteSandbox(ctx context.Context, in *DeleteSandboxRequest, opts ...grpc.CallOption) (*DeleteSandboxResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteSandboxResponse) + err := c.cc.Invoke(ctx, OpenShell_DeleteSandbox_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) CreateSshSession(ctx context.Context, in *CreateSshSessionRequest, opts ...grpc.CallOption) (*CreateSshSessionResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateSshSessionResponse) + err := c.cc.Invoke(ctx, OpenShell_CreateSshSession_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ExposeService(ctx context.Context, in *ExposeServiceRequest, opts ...grpc.CallOption) (*ServiceEndpointResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ServiceEndpointResponse) + err := c.cc.Invoke(ctx, OpenShell_ExposeService_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetService(ctx context.Context, in *GetServiceRequest, opts ...grpc.CallOption) (*ServiceEndpointResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ServiceEndpointResponse) + err := c.cc.Invoke(ctx, OpenShell_GetService_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ListServices(ctx context.Context, in *ListServicesRequest, opts ...grpc.CallOption) (*ListServicesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListServicesResponse) + err := c.cc.Invoke(ctx, OpenShell_ListServices_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) DeleteService(ctx context.Context, in *DeleteServiceRequest, opts ...grpc.CallOption) (*DeleteServiceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteServiceResponse) + err := c.cc.Invoke(ctx, OpenShell_DeleteService_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) RevokeSshSession(ctx context.Context, in *RevokeSshSessionRequest, opts ...grpc.CallOption) (*RevokeSshSessionResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RevokeSshSessionResponse) + err := c.cc.Invoke(ctx, OpenShell_RevokeSshSession_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ExecSandbox(ctx context.Context, in *ExecSandboxRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ExecSandboxEvent], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &OpenShell_ServiceDesc.Streams[0], OpenShell_ExecSandbox_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[ExecSandboxRequest, ExecSandboxEvent]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type OpenShell_ExecSandboxClient = grpc.ServerStreamingClient[ExecSandboxEvent] + +func (c *openShellClient) ForwardTcp(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[TcpForwardFrame, TcpForwardFrame], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &OpenShell_ServiceDesc.Streams[1], OpenShell_ForwardTcp_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[TcpForwardFrame, TcpForwardFrame]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type OpenShell_ForwardTcpClient = grpc.BidiStreamingClient[TcpForwardFrame, TcpForwardFrame] + +func (c *openShellClient) ExecSandboxInteractive(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ExecSandboxInput, ExecSandboxEvent], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &OpenShell_ServiceDesc.Streams[2], OpenShell_ExecSandboxInteractive_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[ExecSandboxInput, ExecSandboxEvent]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type OpenShell_ExecSandboxInteractiveClient = grpc.BidiStreamingClient[ExecSandboxInput, ExecSandboxEvent] + +func (c *openShellClient) CreateProvider(ctx context.Context, in *CreateProviderRequest, opts ...grpc.CallOption) (*ProviderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ProviderResponse) + err := c.cc.Invoke(ctx, OpenShell_CreateProvider_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetProvider(ctx context.Context, in *GetProviderRequest, opts ...grpc.CallOption) (*ProviderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ProviderResponse) + err := c.cc.Invoke(ctx, OpenShell_GetProvider_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ListProviders(ctx context.Context, in *ListProvidersRequest, opts ...grpc.CallOption) (*ListProvidersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListProvidersResponse) + err := c.cc.Invoke(ctx, OpenShell_ListProviders_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ListProviderProfiles(ctx context.Context, in *ListProviderProfilesRequest, opts ...grpc.CallOption) (*ListProviderProfilesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListProviderProfilesResponse) + err := c.cc.Invoke(ctx, OpenShell_ListProviderProfiles_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetProviderProfile(ctx context.Context, in *GetProviderProfileRequest, opts ...grpc.CallOption) (*ProviderProfileResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ProviderProfileResponse) + err := c.cc.Invoke(ctx, OpenShell_GetProviderProfile_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ImportProviderProfiles(ctx context.Context, in *ImportProviderProfilesRequest, opts ...grpc.CallOption) (*ImportProviderProfilesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ImportProviderProfilesResponse) + err := c.cc.Invoke(ctx, OpenShell_ImportProviderProfiles_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) UpdateProviderProfiles(ctx context.Context, in *UpdateProviderProfilesRequest, opts ...grpc.CallOption) (*UpdateProviderProfilesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateProviderProfilesResponse) + err := c.cc.Invoke(ctx, OpenShell_UpdateProviderProfiles_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) LintProviderProfiles(ctx context.Context, in *LintProviderProfilesRequest, opts ...grpc.CallOption) (*LintProviderProfilesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LintProviderProfilesResponse) + err := c.cc.Invoke(ctx, OpenShell_LintProviderProfiles_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) UpdateProvider(ctx context.Context, in *UpdateProviderRequest, opts ...grpc.CallOption) (*ProviderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ProviderResponse) + err := c.cc.Invoke(ctx, OpenShell_UpdateProvider_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetProviderRefreshStatus(ctx context.Context, in *GetProviderRefreshStatusRequest, opts ...grpc.CallOption) (*GetProviderRefreshStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetProviderRefreshStatusResponse) + err := c.cc.Invoke(ctx, OpenShell_GetProviderRefreshStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ConfigureProviderRefresh(ctx context.Context, in *ConfigureProviderRefreshRequest, opts ...grpc.CallOption) (*ConfigureProviderRefreshResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ConfigureProviderRefreshResponse) + err := c.cc.Invoke(ctx, OpenShell_ConfigureProviderRefresh_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) RotateProviderCredential(ctx context.Context, in *RotateProviderCredentialRequest, opts ...grpc.CallOption) (*RotateProviderCredentialResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RotateProviderCredentialResponse) + err := c.cc.Invoke(ctx, OpenShell_RotateProviderCredential_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) DeleteProviderRefresh(ctx context.Context, in *DeleteProviderRefreshRequest, opts ...grpc.CallOption) (*DeleteProviderRefreshResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteProviderRefreshResponse) + err := c.cc.Invoke(ctx, OpenShell_DeleteProviderRefresh_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) DeleteProvider(ctx context.Context, in *DeleteProviderRequest, opts ...grpc.CallOption) (*DeleteProviderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteProviderResponse) + err := c.cc.Invoke(ctx, OpenShell_DeleteProvider_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) DeleteProviderProfile(ctx context.Context, in *DeleteProviderProfileRequest, opts ...grpc.CallOption) (*DeleteProviderProfileResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteProviderProfileResponse) + err := c.cc.Invoke(ctx, OpenShell_DeleteProviderProfile_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetSandboxConfig(ctx context.Context, in *sandboxv1.GetSandboxConfigRequest, opts ...grpc.CallOption) (*sandboxv1.GetSandboxConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(sandboxv1.GetSandboxConfigResponse) + err := c.cc.Invoke(ctx, OpenShell_GetSandboxConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetGatewayConfig(ctx context.Context, in *sandboxv1.GetGatewayConfigRequest, opts ...grpc.CallOption) (*sandboxv1.GetGatewayConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(sandboxv1.GetGatewayConfigResponse) + err := c.cc.Invoke(ctx, OpenShell_GetGatewayConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) UpdateConfig(ctx context.Context, in *UpdateConfigRequest, opts ...grpc.CallOption) (*UpdateConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateConfigResponse) + err := c.cc.Invoke(ctx, OpenShell_UpdateConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetSandboxPolicyStatus(ctx context.Context, in *GetSandboxPolicyStatusRequest, opts ...grpc.CallOption) (*GetSandboxPolicyStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetSandboxPolicyStatusResponse) + err := c.cc.Invoke(ctx, OpenShell_GetSandboxPolicyStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ListSandboxPolicies(ctx context.Context, in *ListSandboxPoliciesRequest, opts ...grpc.CallOption) (*ListSandboxPoliciesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListSandboxPoliciesResponse) + err := c.cc.Invoke(ctx, OpenShell_ListSandboxPolicies_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ReportPolicyStatus(ctx context.Context, in *ReportPolicyStatusRequest, opts ...grpc.CallOption) (*ReportPolicyStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReportPolicyStatusResponse) + err := c.cc.Invoke(ctx, OpenShell_ReportPolicyStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetSandboxProviderEnvironment(ctx context.Context, in *GetSandboxProviderEnvironmentRequest, opts ...grpc.CallOption) (*GetSandboxProviderEnvironmentResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetSandboxProviderEnvironmentResponse) + err := c.cc.Invoke(ctx, OpenShell_GetSandboxProviderEnvironment_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetSandboxLogs(ctx context.Context, in *GetSandboxLogsRequest, opts ...grpc.CallOption) (*GetSandboxLogsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetSandboxLogsResponse) + err := c.cc.Invoke(ctx, OpenShell_GetSandboxLogs_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) PushSandboxLogs(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[PushSandboxLogsRequest, PushSandboxLogsResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &OpenShell_ServiceDesc.Streams[3], OpenShell_PushSandboxLogs_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[PushSandboxLogsRequest, PushSandboxLogsResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type OpenShell_PushSandboxLogsClient = grpc.ClientStreamingClient[PushSandboxLogsRequest, PushSandboxLogsResponse] + +func (c *openShellClient) ConnectSupervisor(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SupervisorMessage, GatewayMessage], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &OpenShell_ServiceDesc.Streams[4], OpenShell_ConnectSupervisor_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[SupervisorMessage, GatewayMessage]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type OpenShell_ConnectSupervisorClient = grpc.BidiStreamingClient[SupervisorMessage, GatewayMessage] + +func (c *openShellClient) RelayStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[RelayFrame, RelayFrame], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &OpenShell_ServiceDesc.Streams[5], OpenShell_RelayStream_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[RelayFrame, RelayFrame]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type OpenShell_RelayStreamClient = grpc.BidiStreamingClient[RelayFrame, RelayFrame] + +func (c *openShellClient) WatchSandbox(ctx context.Context, in *WatchSandboxRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[SandboxStreamEvent], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &OpenShell_ServiceDesc.Streams[6], OpenShell_WatchSandbox_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[WatchSandboxRequest, SandboxStreamEvent]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type OpenShell_WatchSandboxClient = grpc.ServerStreamingClient[SandboxStreamEvent] + +func (c *openShellClient) SubmitPolicyAnalysis(ctx context.Context, in *SubmitPolicyAnalysisRequest, opts ...grpc.CallOption) (*SubmitPolicyAnalysisResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SubmitPolicyAnalysisResponse) + err := c.cc.Invoke(ctx, OpenShell_SubmitPolicyAnalysis_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetDraftPolicy(ctx context.Context, in *GetDraftPolicyRequest, opts ...grpc.CallOption) (*GetDraftPolicyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetDraftPolicyResponse) + err := c.cc.Invoke(ctx, OpenShell_GetDraftPolicy_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ApproveDraftChunk(ctx context.Context, in *ApproveDraftChunkRequest, opts ...grpc.CallOption) (*ApproveDraftChunkResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ApproveDraftChunkResponse) + err := c.cc.Invoke(ctx, OpenShell_ApproveDraftChunk_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) RejectDraftChunk(ctx context.Context, in *RejectDraftChunkRequest, opts ...grpc.CallOption) (*RejectDraftChunkResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RejectDraftChunkResponse) + err := c.cc.Invoke(ctx, OpenShell_RejectDraftChunk_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ApproveAllDraftChunks(ctx context.Context, in *ApproveAllDraftChunksRequest, opts ...grpc.CallOption) (*ApproveAllDraftChunksResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ApproveAllDraftChunksResponse) + err := c.cc.Invoke(ctx, OpenShell_ApproveAllDraftChunks_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) EditDraftChunk(ctx context.Context, in *EditDraftChunkRequest, opts ...grpc.CallOption) (*EditDraftChunkResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(EditDraftChunkResponse) + err := c.cc.Invoke(ctx, OpenShell_EditDraftChunk_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) UndoDraftChunk(ctx context.Context, in *UndoDraftChunkRequest, opts ...grpc.CallOption) (*UndoDraftChunkResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UndoDraftChunkResponse) + err := c.cc.Invoke(ctx, OpenShell_UndoDraftChunk_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ClearDraftChunks(ctx context.Context, in *ClearDraftChunksRequest, opts ...grpc.CallOption) (*ClearDraftChunksResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ClearDraftChunksResponse) + err := c.cc.Invoke(ctx, OpenShell_ClearDraftChunks_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetDraftHistory(ctx context.Context, in *GetDraftHistoryRequest, opts ...grpc.CallOption) (*GetDraftHistoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetDraftHistoryResponse) + err := c.cc.Invoke(ctx, OpenShell_GetDraftHistory_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) IssueSandboxToken(ctx context.Context, in *IssueSandboxTokenRequest, opts ...grpc.CallOption) (*IssueSandboxTokenResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(IssueSandboxTokenResponse) + err := c.cc.Invoke(ctx, OpenShell_IssueSandboxToken_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) RefreshSandboxToken(ctx context.Context, in *RefreshSandboxTokenRequest, opts ...grpc.CallOption) (*RefreshSandboxTokenResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RefreshSandboxTokenResponse) + err := c.cc.Invoke(ctx, OpenShell_RefreshSandboxToken_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) CreateWorkspace(ctx context.Context, in *CreateWorkspaceRequest, opts ...grpc.CallOption) (*CreateWorkspaceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateWorkspaceResponse) + err := c.cc.Invoke(ctx, OpenShell_CreateWorkspace_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetWorkspace(ctx context.Context, in *GetWorkspaceRequest, opts ...grpc.CallOption) (*GetWorkspaceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetWorkspaceResponse) + err := c.cc.Invoke(ctx, OpenShell_GetWorkspace_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ListWorkspaces(ctx context.Context, in *ListWorkspacesRequest, opts ...grpc.CallOption) (*ListWorkspacesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListWorkspacesResponse) + err := c.cc.Invoke(ctx, OpenShell_ListWorkspaces_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) DeleteWorkspace(ctx context.Context, in *DeleteWorkspaceRequest, opts ...grpc.CallOption) (*DeleteWorkspaceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteWorkspaceResponse) + err := c.cc.Invoke(ctx, OpenShell_DeleteWorkspace_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) AddWorkspaceMember(ctx context.Context, in *AddWorkspaceMemberRequest, opts ...grpc.CallOption) (*AddWorkspaceMemberResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AddWorkspaceMemberResponse) + err := c.cc.Invoke(ctx, OpenShell_AddWorkspaceMember_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) RemoveWorkspaceMember(ctx context.Context, in *RemoveWorkspaceMemberRequest, opts ...grpc.CallOption) (*RemoveWorkspaceMemberResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RemoveWorkspaceMemberResponse) + err := c.cc.Invoke(ctx, OpenShell_RemoveWorkspaceMember_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ListWorkspaceMembers(ctx context.Context, in *ListWorkspaceMembersRequest, opts ...grpc.CallOption) (*ListWorkspaceMembersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListWorkspaceMembersResponse) + err := c.cc.Invoke(ctx, OpenShell_ListWorkspaceMembers_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// OpenShellServer is the server API for OpenShell service. +// All implementations must embed UnimplementedOpenShellServer +// for forward compatibility. +// +// 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. +type OpenShellServer interface { + // Check the health of the service. + Health(context.Context, *HealthRequest) (*HealthResponse, error) + // Return the authenticated caller identity established by the gateway. + GetCurrentUser(context.Context, *GetCurrentUserRequest) (*GetCurrentUserResponse, error) + // Fetch elevated live gateway runtime metadata. + GetGatewayInfo(context.Context, *GetGatewayInfoRequest) (*GetGatewayInfoResponse, error) + // Create a new sandbox. + CreateSandbox(context.Context, *CreateSandboxRequest) (*SandboxResponse, error) + // Fetch a sandbox by name. + GetSandbox(context.Context, *GetSandboxRequest) (*SandboxResponse, error) + // List sandboxes. + ListSandboxes(context.Context, *ListSandboxesRequest) (*ListSandboxesResponse, error) + // List provider records attached to a sandbox. + ListSandboxProviders(context.Context, *ListSandboxProvidersRequest) (*ListSandboxProvidersResponse, error) + // Attach a provider record to an existing sandbox. + AttachSandboxProvider(context.Context, *AttachSandboxProviderRequest) (*AttachSandboxProviderResponse, error) + // Detach a provider record from an existing sandbox. + DetachSandboxProvider(context.Context, *DetachSandboxProviderRequest) (*DetachSandboxProviderResponse, error) + // Delete a sandbox by name. + DeleteSandbox(context.Context, *DeleteSandboxRequest) (*DeleteSandboxResponse, error) + // Create a short-lived SSH session for a sandbox. + CreateSshSession(context.Context, *CreateSshSessionRequest) (*CreateSshSessionResponse, error) + // Create or update a sandbox HTTP service endpoint for local routing. + ExposeService(context.Context, *ExposeServiceRequest) (*ServiceEndpointResponse, error) + // Fetch one sandbox HTTP service endpoint. + GetService(context.Context, *GetServiceRequest) (*ServiceEndpointResponse, error) + // List sandbox HTTP service endpoints. + ListServices(context.Context, *ListServicesRequest) (*ListServicesResponse, error) + // Delete one sandbox HTTP service endpoint. + DeleteService(context.Context, *DeleteServiceRequest) (*DeleteServiceResponse, error) + // Revoke a previously issued SSH session. + RevokeSshSession(context.Context, *RevokeSshSessionRequest) (*RevokeSshSessionResponse, error) + // Execute a command in a ready sandbox and stream output. + ExecSandbox(*ExecSandboxRequest, grpc.ServerStreamingServer[ExecSandboxEvent]) error + // Forward one CLI-side TCP connection to a loopback TCP target in a sandbox. + ForwardTcp(grpc.BidiStreamingServer[TcpForwardFrame, TcpForwardFrame]) error + // 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. + ExecSandboxInteractive(grpc.BidiStreamingServer[ExecSandboxInput, ExecSandboxEvent]) error + // Create a provider. + CreateProvider(context.Context, *CreateProviderRequest) (*ProviderResponse, error) + // Fetch a provider by name. + GetProvider(context.Context, *GetProviderRequest) (*ProviderResponse, error) + // List providers. + ListProviders(context.Context, *ListProvidersRequest) (*ListProvidersResponse, error) + // List available provider type profiles. + ListProviderProfiles(context.Context, *ListProviderProfilesRequest) (*ListProviderProfilesResponse, error) + // Fetch one provider type profile by id. + GetProviderProfile(context.Context, *GetProviderProfileRequest) (*ProviderProfileResponse, error) + // Import custom provider type profiles. + ImportProviderProfiles(context.Context, *ImportProviderProfilesRequest) (*ImportProviderProfilesResponse, error) + // Update an existing custom provider type profile. + UpdateProviderProfiles(context.Context, *UpdateProviderProfilesRequest) (*UpdateProviderProfilesResponse, error) + // Validate provider type profiles without registering them. + LintProviderProfiles(context.Context, *LintProviderProfilesRequest) (*LintProviderProfilesResponse, error) + // Update an existing provider by name. + UpdateProvider(context.Context, *UpdateProviderRequest) (*ProviderResponse, error) + // Fetch refresh status for one provider or provider credential. + GetProviderRefreshStatus(context.Context, *GetProviderRefreshStatusRequest) (*GetProviderRefreshStatusResponse, error) + // Configure gateway-owned refresh material for one provider credential. + ConfigureProviderRefresh(context.Context, *ConfigureProviderRefreshRequest) (*ConfigureProviderRefreshResponse, error) + // Record a gateway-owned refresh request for one provider credential. + RotateProviderCredential(context.Context, *RotateProviderCredentialRequest) (*RotateProviderCredentialResponse, error) + // Delete gateway-owned refresh configuration for one provider credential. + DeleteProviderRefresh(context.Context, *DeleteProviderRefreshRequest) (*DeleteProviderRefreshResponse, error) + // Delete a provider by name. + DeleteProvider(context.Context, *DeleteProviderRequest) (*DeleteProviderResponse, error) + // Delete a custom provider type profile by id. + DeleteProviderProfile(context.Context, *DeleteProviderProfileRequest) (*DeleteProviderProfileResponse, error) + // Get sandbox settings by id (called by sandbox entrypoint and poll loop). + GetSandboxConfig(context.Context, *sandboxv1.GetSandboxConfigRequest) (*sandboxv1.GetSandboxConfigResponse, error) + // Get gateway-global settings (read-only feature flags; any authenticated + // user may read these so the CLI and TUI can discover capabilities like + // providers_v2_enabled 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. + GetGatewayConfig(context.Context, *sandboxv1.GetGatewayConfigRequest) (*sandboxv1.GetGatewayConfigResponse, error) + // Update settings or policy at sandbox or global scope. + UpdateConfig(context.Context, *UpdateConfigRequest) (*UpdateConfigResponse, error) + // Get the load status of a specific policy version. + GetSandboxPolicyStatus(context.Context, *GetSandboxPolicyStatusRequest) (*GetSandboxPolicyStatusResponse, error) + // List policy history for a sandbox. + ListSandboxPolicies(context.Context, *ListSandboxPoliciesRequest) (*ListSandboxPoliciesResponse, error) + // Report policy load result (called by sandbox after reload attempt). + ReportPolicyStatus(context.Context, *ReportPolicyStatusRequest) (*ReportPolicyStatusResponse, error) + // Get provider environment for a sandbox (called by sandbox supervisor at startup). + GetSandboxProviderEnvironment(context.Context, *GetSandboxProviderEnvironmentRequest) (*GetSandboxProviderEnvironmentResponse, error) + // Fetch recent sandbox logs (one-shot). + GetSandboxLogs(context.Context, *GetSandboxLogsRequest) (*GetSandboxLogsResponse, error) + // Push sandbox supervisor logs to the server (client-streaming). + PushSandboxLogs(grpc.ClientStreamingServer[PushSandboxLogsRequest, PushSandboxLogsResponse]) error + // 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. + ConnectSupervisor(grpc.BidiStreamingServer[SupervisorMessage, GatewayMessage]) error + // 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. + RelayStream(grpc.BidiStreamingServer[RelayFrame, RelayFrame]) error + // 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 + WatchSandbox(*WatchSandboxRequest, grpc.ServerStreamingServer[SandboxStreamEvent]) error + // Submit denial analysis results from sandbox (summaries + proposed chunks). + SubmitPolicyAnalysis(context.Context, *SubmitPolicyAnalysisRequest) (*SubmitPolicyAnalysisResponse, error) + // Get draft policy recommendations for a sandbox. + GetDraftPolicy(context.Context, *GetDraftPolicyRequest) (*GetDraftPolicyResponse, error) + // Approve a single draft policy chunk (merges into active policy). + ApproveDraftChunk(context.Context, *ApproveDraftChunkRequest) (*ApproveDraftChunkResponse, error) + // Reject a single draft policy chunk. + RejectDraftChunk(context.Context, *RejectDraftChunkRequest) (*RejectDraftChunkResponse, error) + // Approve all pending draft chunks (skips security-flagged unless forced). + ApproveAllDraftChunks(context.Context, *ApproveAllDraftChunksRequest) (*ApproveAllDraftChunksResponse, error) + // Edit a pending draft chunk in-place (e.g. narrow allowed_ips). + EditDraftChunk(context.Context, *EditDraftChunkRequest) (*EditDraftChunkResponse, error) + // Reverse an approval (remove merged rule from active policy). + UndoDraftChunk(context.Context, *UndoDraftChunkRequest) (*UndoDraftChunkResponse, error) + // Clear all pending draft chunks for a sandbox. + ClearDraftChunks(context.Context, *ClearDraftChunksRequest) (*ClearDraftChunksResponse, error) + // Get decision history for a sandbox's draft policy. + GetDraftHistory(context.Context, *GetDraftHistoryRequest) (*GetDraftHistoryResponse, error) + // 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. + IssueSandboxToken(context.Context, *IssueSandboxTokenRequest) (*IssueSandboxTokenResponse, error) + // 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. + RefreshSandboxToken(context.Context, *RefreshSandboxTokenRequest) (*RefreshSandboxTokenResponse, error) + // Create a workspace. + CreateWorkspace(context.Context, *CreateWorkspaceRequest) (*CreateWorkspaceResponse, error) + // Fetch a workspace by name. + GetWorkspace(context.Context, *GetWorkspaceRequest) (*GetWorkspaceResponse, error) + // List workspaces. + ListWorkspaces(context.Context, *ListWorkspacesRequest) (*ListWorkspacesResponse, error) + // Delete a workspace by name. + DeleteWorkspace(context.Context, *DeleteWorkspaceRequest) (*DeleteWorkspaceResponse, error) + // Add a member to a workspace. + AddWorkspaceMember(context.Context, *AddWorkspaceMemberRequest) (*AddWorkspaceMemberResponse, error) + // Remove a member from a workspace. + RemoveWorkspaceMember(context.Context, *RemoveWorkspaceMemberRequest) (*RemoveWorkspaceMemberResponse, error) + // List members of a workspace. + ListWorkspaceMembers(context.Context, *ListWorkspaceMembersRequest) (*ListWorkspaceMembersResponse, error) + mustEmbedUnimplementedOpenShellServer() +} + +// UnimplementedOpenShellServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedOpenShellServer struct{} + +func (UnimplementedOpenShellServer) Health(context.Context, *HealthRequest) (*HealthResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Health not implemented") +} +func (UnimplementedOpenShellServer) GetCurrentUser(context.Context, *GetCurrentUserRequest) (*GetCurrentUserResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetCurrentUser not implemented") +} +func (UnimplementedOpenShellServer) GetGatewayInfo(context.Context, *GetGatewayInfoRequest) (*GetGatewayInfoResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetGatewayInfo not implemented") +} +func (UnimplementedOpenShellServer) CreateSandbox(context.Context, *CreateSandboxRequest) (*SandboxResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateSandbox not implemented") +} +func (UnimplementedOpenShellServer) GetSandbox(context.Context, *GetSandboxRequest) (*SandboxResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetSandbox not implemented") +} +func (UnimplementedOpenShellServer) ListSandboxes(context.Context, *ListSandboxesRequest) (*ListSandboxesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListSandboxes not implemented") +} +func (UnimplementedOpenShellServer) ListSandboxProviders(context.Context, *ListSandboxProvidersRequest) (*ListSandboxProvidersResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListSandboxProviders not implemented") +} +func (UnimplementedOpenShellServer) AttachSandboxProvider(context.Context, *AttachSandboxProviderRequest) (*AttachSandboxProviderResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AttachSandboxProvider not implemented") +} +func (UnimplementedOpenShellServer) DetachSandboxProvider(context.Context, *DetachSandboxProviderRequest) (*DetachSandboxProviderResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DetachSandboxProvider not implemented") +} +func (UnimplementedOpenShellServer) DeleteSandbox(context.Context, *DeleteSandboxRequest) (*DeleteSandboxResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteSandbox not implemented") +} +func (UnimplementedOpenShellServer) CreateSshSession(context.Context, *CreateSshSessionRequest) (*CreateSshSessionResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateSshSession not implemented") +} +func (UnimplementedOpenShellServer) ExposeService(context.Context, *ExposeServiceRequest) (*ServiceEndpointResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ExposeService not implemented") +} +func (UnimplementedOpenShellServer) GetService(context.Context, *GetServiceRequest) (*ServiceEndpointResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetService not implemented") +} +func (UnimplementedOpenShellServer) ListServices(context.Context, *ListServicesRequest) (*ListServicesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListServices not implemented") +} +func (UnimplementedOpenShellServer) DeleteService(context.Context, *DeleteServiceRequest) (*DeleteServiceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteService not implemented") +} +func (UnimplementedOpenShellServer) RevokeSshSession(context.Context, *RevokeSshSessionRequest) (*RevokeSshSessionResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RevokeSshSession not implemented") +} +func (UnimplementedOpenShellServer) ExecSandbox(*ExecSandboxRequest, grpc.ServerStreamingServer[ExecSandboxEvent]) error { + return status.Error(codes.Unimplemented, "method ExecSandbox not implemented") +} +func (UnimplementedOpenShellServer) ForwardTcp(grpc.BidiStreamingServer[TcpForwardFrame, TcpForwardFrame]) error { + return status.Error(codes.Unimplemented, "method ForwardTcp not implemented") +} +func (UnimplementedOpenShellServer) ExecSandboxInteractive(grpc.BidiStreamingServer[ExecSandboxInput, ExecSandboxEvent]) error { + return status.Error(codes.Unimplemented, "method ExecSandboxInteractive not implemented") +} +func (UnimplementedOpenShellServer) CreateProvider(context.Context, *CreateProviderRequest) (*ProviderResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateProvider not implemented") +} +func (UnimplementedOpenShellServer) GetProvider(context.Context, *GetProviderRequest) (*ProviderResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetProvider not implemented") +} +func (UnimplementedOpenShellServer) ListProviders(context.Context, *ListProvidersRequest) (*ListProvidersResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListProviders not implemented") +} +func (UnimplementedOpenShellServer) ListProviderProfiles(context.Context, *ListProviderProfilesRequest) (*ListProviderProfilesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListProviderProfiles not implemented") +} +func (UnimplementedOpenShellServer) GetProviderProfile(context.Context, *GetProviderProfileRequest) (*ProviderProfileResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetProviderProfile not implemented") +} +func (UnimplementedOpenShellServer) ImportProviderProfiles(context.Context, *ImportProviderProfilesRequest) (*ImportProviderProfilesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ImportProviderProfiles not implemented") +} +func (UnimplementedOpenShellServer) UpdateProviderProfiles(context.Context, *UpdateProviderProfilesRequest) (*UpdateProviderProfilesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateProviderProfiles not implemented") +} +func (UnimplementedOpenShellServer) LintProviderProfiles(context.Context, *LintProviderProfilesRequest) (*LintProviderProfilesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method LintProviderProfiles not implemented") +} +func (UnimplementedOpenShellServer) UpdateProvider(context.Context, *UpdateProviderRequest) (*ProviderResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateProvider not implemented") +} +func (UnimplementedOpenShellServer) GetProviderRefreshStatus(context.Context, *GetProviderRefreshStatusRequest) (*GetProviderRefreshStatusResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetProviderRefreshStatus not implemented") +} +func (UnimplementedOpenShellServer) ConfigureProviderRefresh(context.Context, *ConfigureProviderRefreshRequest) (*ConfigureProviderRefreshResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ConfigureProviderRefresh not implemented") +} +func (UnimplementedOpenShellServer) RotateProviderCredential(context.Context, *RotateProviderCredentialRequest) (*RotateProviderCredentialResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RotateProviderCredential not implemented") +} +func (UnimplementedOpenShellServer) DeleteProviderRefresh(context.Context, *DeleteProviderRefreshRequest) (*DeleteProviderRefreshResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteProviderRefresh not implemented") +} +func (UnimplementedOpenShellServer) DeleteProvider(context.Context, *DeleteProviderRequest) (*DeleteProviderResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteProvider not implemented") +} +func (UnimplementedOpenShellServer) DeleteProviderProfile(context.Context, *DeleteProviderProfileRequest) (*DeleteProviderProfileResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteProviderProfile not implemented") +} +func (UnimplementedOpenShellServer) GetSandboxConfig(context.Context, *sandboxv1.GetSandboxConfigRequest) (*sandboxv1.GetSandboxConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetSandboxConfig not implemented") +} +func (UnimplementedOpenShellServer) GetGatewayConfig(context.Context, *sandboxv1.GetGatewayConfigRequest) (*sandboxv1.GetGatewayConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetGatewayConfig not implemented") +} +func (UnimplementedOpenShellServer) UpdateConfig(context.Context, *UpdateConfigRequest) (*UpdateConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateConfig not implemented") +} +func (UnimplementedOpenShellServer) GetSandboxPolicyStatus(context.Context, *GetSandboxPolicyStatusRequest) (*GetSandboxPolicyStatusResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetSandboxPolicyStatus not implemented") +} +func (UnimplementedOpenShellServer) ListSandboxPolicies(context.Context, *ListSandboxPoliciesRequest) (*ListSandboxPoliciesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListSandboxPolicies not implemented") +} +func (UnimplementedOpenShellServer) ReportPolicyStatus(context.Context, *ReportPolicyStatusRequest) (*ReportPolicyStatusResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ReportPolicyStatus not implemented") +} +func (UnimplementedOpenShellServer) GetSandboxProviderEnvironment(context.Context, *GetSandboxProviderEnvironmentRequest) (*GetSandboxProviderEnvironmentResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetSandboxProviderEnvironment not implemented") +} +func (UnimplementedOpenShellServer) GetSandboxLogs(context.Context, *GetSandboxLogsRequest) (*GetSandboxLogsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetSandboxLogs not implemented") +} +func (UnimplementedOpenShellServer) PushSandboxLogs(grpc.ClientStreamingServer[PushSandboxLogsRequest, PushSandboxLogsResponse]) error { + return status.Error(codes.Unimplemented, "method PushSandboxLogs not implemented") +} +func (UnimplementedOpenShellServer) ConnectSupervisor(grpc.BidiStreamingServer[SupervisorMessage, GatewayMessage]) error { + return status.Error(codes.Unimplemented, "method ConnectSupervisor not implemented") +} +func (UnimplementedOpenShellServer) RelayStream(grpc.BidiStreamingServer[RelayFrame, RelayFrame]) error { + return status.Error(codes.Unimplemented, "method RelayStream not implemented") +} +func (UnimplementedOpenShellServer) WatchSandbox(*WatchSandboxRequest, grpc.ServerStreamingServer[SandboxStreamEvent]) error { + return status.Error(codes.Unimplemented, "method WatchSandbox not implemented") +} +func (UnimplementedOpenShellServer) SubmitPolicyAnalysis(context.Context, *SubmitPolicyAnalysisRequest) (*SubmitPolicyAnalysisResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SubmitPolicyAnalysis not implemented") +} +func (UnimplementedOpenShellServer) GetDraftPolicy(context.Context, *GetDraftPolicyRequest) (*GetDraftPolicyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetDraftPolicy not implemented") +} +func (UnimplementedOpenShellServer) ApproveDraftChunk(context.Context, *ApproveDraftChunkRequest) (*ApproveDraftChunkResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ApproveDraftChunk not implemented") +} +func (UnimplementedOpenShellServer) RejectDraftChunk(context.Context, *RejectDraftChunkRequest) (*RejectDraftChunkResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RejectDraftChunk not implemented") +} +func (UnimplementedOpenShellServer) ApproveAllDraftChunks(context.Context, *ApproveAllDraftChunksRequest) (*ApproveAllDraftChunksResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ApproveAllDraftChunks not implemented") +} +func (UnimplementedOpenShellServer) EditDraftChunk(context.Context, *EditDraftChunkRequest) (*EditDraftChunkResponse, error) { + return nil, status.Error(codes.Unimplemented, "method EditDraftChunk not implemented") +} +func (UnimplementedOpenShellServer) UndoDraftChunk(context.Context, *UndoDraftChunkRequest) (*UndoDraftChunkResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UndoDraftChunk not implemented") +} +func (UnimplementedOpenShellServer) ClearDraftChunks(context.Context, *ClearDraftChunksRequest) (*ClearDraftChunksResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ClearDraftChunks not implemented") +} +func (UnimplementedOpenShellServer) GetDraftHistory(context.Context, *GetDraftHistoryRequest) (*GetDraftHistoryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetDraftHistory not implemented") +} +func (UnimplementedOpenShellServer) IssueSandboxToken(context.Context, *IssueSandboxTokenRequest) (*IssueSandboxTokenResponse, error) { + return nil, status.Error(codes.Unimplemented, "method IssueSandboxToken not implemented") +} +func (UnimplementedOpenShellServer) RefreshSandboxToken(context.Context, *RefreshSandboxTokenRequest) (*RefreshSandboxTokenResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RefreshSandboxToken not implemented") +} +func (UnimplementedOpenShellServer) CreateWorkspace(context.Context, *CreateWorkspaceRequest) (*CreateWorkspaceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateWorkspace not implemented") +} +func (UnimplementedOpenShellServer) GetWorkspace(context.Context, *GetWorkspaceRequest) (*GetWorkspaceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetWorkspace not implemented") +} +func (UnimplementedOpenShellServer) ListWorkspaces(context.Context, *ListWorkspacesRequest) (*ListWorkspacesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListWorkspaces not implemented") +} +func (UnimplementedOpenShellServer) DeleteWorkspace(context.Context, *DeleteWorkspaceRequest) (*DeleteWorkspaceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteWorkspace not implemented") +} +func (UnimplementedOpenShellServer) AddWorkspaceMember(context.Context, *AddWorkspaceMemberRequest) (*AddWorkspaceMemberResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AddWorkspaceMember not implemented") +} +func (UnimplementedOpenShellServer) RemoveWorkspaceMember(context.Context, *RemoveWorkspaceMemberRequest) (*RemoveWorkspaceMemberResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RemoveWorkspaceMember not implemented") +} +func (UnimplementedOpenShellServer) ListWorkspaceMembers(context.Context, *ListWorkspaceMembersRequest) (*ListWorkspaceMembersResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListWorkspaceMembers not implemented") +} +func (UnimplementedOpenShellServer) mustEmbedUnimplementedOpenShellServer() {} +func (UnimplementedOpenShellServer) testEmbeddedByValue() {} + +// UnsafeOpenShellServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to OpenShellServer will +// result in compilation errors. +type UnsafeOpenShellServer interface { + mustEmbedUnimplementedOpenShellServer() +} + +func RegisterOpenShellServer(s grpc.ServiceRegistrar, srv OpenShellServer) { + // If the following call panics, it indicates UnimplementedOpenShellServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&OpenShell_ServiceDesc, srv) +} + +func _OpenShell_Health_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HealthRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).Health(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_Health_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).Health(ctx, req.(*HealthRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetCurrentUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCurrentUserRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetCurrentUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetCurrentUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetCurrentUser(ctx, req.(*GetCurrentUserRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetGatewayInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetGatewayInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetGatewayInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetGatewayInfo_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetGatewayInfo(ctx, req.(*GetGatewayInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_CreateSandbox_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateSandboxRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).CreateSandbox(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_CreateSandbox_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).CreateSandbox(ctx, req.(*CreateSandboxRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetSandbox_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetSandboxRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetSandbox(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetSandbox_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetSandbox(ctx, req.(*GetSandboxRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ListSandboxes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListSandboxesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ListSandboxes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ListSandboxes_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ListSandboxes(ctx, req.(*ListSandboxesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ListSandboxProviders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListSandboxProvidersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ListSandboxProviders(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ListSandboxProviders_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ListSandboxProviders(ctx, req.(*ListSandboxProvidersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_AttachSandboxProvider_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AttachSandboxProviderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).AttachSandboxProvider(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_AttachSandboxProvider_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).AttachSandboxProvider(ctx, req.(*AttachSandboxProviderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_DetachSandboxProvider_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DetachSandboxProviderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).DetachSandboxProvider(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_DetachSandboxProvider_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).DetachSandboxProvider(ctx, req.(*DetachSandboxProviderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_DeleteSandbox_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteSandboxRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).DeleteSandbox(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_DeleteSandbox_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).DeleteSandbox(ctx, req.(*DeleteSandboxRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_CreateSshSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateSshSessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).CreateSshSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_CreateSshSession_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).CreateSshSession(ctx, req.(*CreateSshSessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ExposeService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExposeServiceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ExposeService(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ExposeService_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ExposeService(ctx, req.(*ExposeServiceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetServiceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetService(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetService_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetService(ctx, req.(*GetServiceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ListServices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListServicesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ListServices(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ListServices_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ListServices(ctx, req.(*ListServicesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_DeleteService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteServiceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).DeleteService(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_DeleteService_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).DeleteService(ctx, req.(*DeleteServiceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_RevokeSshSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RevokeSshSessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).RevokeSshSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_RevokeSshSession_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).RevokeSshSession(ctx, req.(*RevokeSshSessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ExecSandbox_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(ExecSandboxRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(OpenShellServer).ExecSandbox(m, &grpc.GenericServerStream[ExecSandboxRequest, ExecSandboxEvent]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type OpenShell_ExecSandboxServer = grpc.ServerStreamingServer[ExecSandboxEvent] + +func _OpenShell_ForwardTcp_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(OpenShellServer).ForwardTcp(&grpc.GenericServerStream[TcpForwardFrame, TcpForwardFrame]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type OpenShell_ForwardTcpServer = grpc.BidiStreamingServer[TcpForwardFrame, TcpForwardFrame] + +func _OpenShell_ExecSandboxInteractive_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(OpenShellServer).ExecSandboxInteractive(&grpc.GenericServerStream[ExecSandboxInput, ExecSandboxEvent]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type OpenShell_ExecSandboxInteractiveServer = grpc.BidiStreamingServer[ExecSandboxInput, ExecSandboxEvent] + +func _OpenShell_CreateProvider_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateProviderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).CreateProvider(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_CreateProvider_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).CreateProvider(ctx, req.(*CreateProviderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetProvider_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetProviderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetProvider(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetProvider_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetProvider(ctx, req.(*GetProviderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ListProviders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListProvidersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ListProviders(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ListProviders_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ListProviders(ctx, req.(*ListProvidersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ListProviderProfiles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListProviderProfilesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ListProviderProfiles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ListProviderProfiles_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ListProviderProfiles(ctx, req.(*ListProviderProfilesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetProviderProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetProviderProfileRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetProviderProfile(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetProviderProfile_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetProviderProfile(ctx, req.(*GetProviderProfileRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ImportProviderProfiles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ImportProviderProfilesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ImportProviderProfiles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ImportProviderProfiles_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ImportProviderProfiles(ctx, req.(*ImportProviderProfilesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_UpdateProviderProfiles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateProviderProfilesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).UpdateProviderProfiles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_UpdateProviderProfiles_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).UpdateProviderProfiles(ctx, req.(*UpdateProviderProfilesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_LintProviderProfiles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LintProviderProfilesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).LintProviderProfiles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_LintProviderProfiles_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).LintProviderProfiles(ctx, req.(*LintProviderProfilesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_UpdateProvider_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateProviderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).UpdateProvider(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_UpdateProvider_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).UpdateProvider(ctx, req.(*UpdateProviderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetProviderRefreshStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetProviderRefreshStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetProviderRefreshStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetProviderRefreshStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetProviderRefreshStatus(ctx, req.(*GetProviderRefreshStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ConfigureProviderRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ConfigureProviderRefreshRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ConfigureProviderRefresh(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ConfigureProviderRefresh_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ConfigureProviderRefresh(ctx, req.(*ConfigureProviderRefreshRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_RotateProviderCredential_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RotateProviderCredentialRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).RotateProviderCredential(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_RotateProviderCredential_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).RotateProviderCredential(ctx, req.(*RotateProviderCredentialRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_DeleteProviderRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteProviderRefreshRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).DeleteProviderRefresh(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_DeleteProviderRefresh_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).DeleteProviderRefresh(ctx, req.(*DeleteProviderRefreshRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_DeleteProvider_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteProviderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).DeleteProvider(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_DeleteProvider_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).DeleteProvider(ctx, req.(*DeleteProviderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_DeleteProviderProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteProviderProfileRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).DeleteProviderProfile(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_DeleteProviderProfile_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).DeleteProviderProfile(ctx, req.(*DeleteProviderProfileRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetSandboxConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(sandboxv1.GetSandboxConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetSandboxConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetSandboxConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetSandboxConfig(ctx, req.(*sandboxv1.GetSandboxConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetGatewayConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(sandboxv1.GetGatewayConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetGatewayConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetGatewayConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetGatewayConfig(ctx, req.(*sandboxv1.GetGatewayConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_UpdateConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).UpdateConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_UpdateConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).UpdateConfig(ctx, req.(*UpdateConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetSandboxPolicyStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetSandboxPolicyStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetSandboxPolicyStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetSandboxPolicyStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetSandboxPolicyStatus(ctx, req.(*GetSandboxPolicyStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ListSandboxPolicies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListSandboxPoliciesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ListSandboxPolicies(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ListSandboxPolicies_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ListSandboxPolicies(ctx, req.(*ListSandboxPoliciesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ReportPolicyStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReportPolicyStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ReportPolicyStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ReportPolicyStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ReportPolicyStatus(ctx, req.(*ReportPolicyStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetSandboxProviderEnvironment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetSandboxProviderEnvironmentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetSandboxProviderEnvironment(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetSandboxProviderEnvironment_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetSandboxProviderEnvironment(ctx, req.(*GetSandboxProviderEnvironmentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetSandboxLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetSandboxLogsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetSandboxLogs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetSandboxLogs_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetSandboxLogs(ctx, req.(*GetSandboxLogsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_PushSandboxLogs_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(OpenShellServer).PushSandboxLogs(&grpc.GenericServerStream[PushSandboxLogsRequest, PushSandboxLogsResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type OpenShell_PushSandboxLogsServer = grpc.ClientStreamingServer[PushSandboxLogsRequest, PushSandboxLogsResponse] + +func _OpenShell_ConnectSupervisor_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(OpenShellServer).ConnectSupervisor(&grpc.GenericServerStream[SupervisorMessage, GatewayMessage]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type OpenShell_ConnectSupervisorServer = grpc.BidiStreamingServer[SupervisorMessage, GatewayMessage] + +func _OpenShell_RelayStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(OpenShellServer).RelayStream(&grpc.GenericServerStream[RelayFrame, RelayFrame]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type OpenShell_RelayStreamServer = grpc.BidiStreamingServer[RelayFrame, RelayFrame] + +func _OpenShell_WatchSandbox_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(WatchSandboxRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(OpenShellServer).WatchSandbox(m, &grpc.GenericServerStream[WatchSandboxRequest, SandboxStreamEvent]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type OpenShell_WatchSandboxServer = grpc.ServerStreamingServer[SandboxStreamEvent] + +func _OpenShell_SubmitPolicyAnalysis_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SubmitPolicyAnalysisRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).SubmitPolicyAnalysis(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_SubmitPolicyAnalysis_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).SubmitPolicyAnalysis(ctx, req.(*SubmitPolicyAnalysisRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetDraftPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDraftPolicyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetDraftPolicy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetDraftPolicy_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetDraftPolicy(ctx, req.(*GetDraftPolicyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ApproveDraftChunk_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApproveDraftChunkRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ApproveDraftChunk(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ApproveDraftChunk_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ApproveDraftChunk(ctx, req.(*ApproveDraftChunkRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_RejectDraftChunk_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RejectDraftChunkRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).RejectDraftChunk(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_RejectDraftChunk_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).RejectDraftChunk(ctx, req.(*RejectDraftChunkRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ApproveAllDraftChunks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApproveAllDraftChunksRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ApproveAllDraftChunks(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ApproveAllDraftChunks_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ApproveAllDraftChunks(ctx, req.(*ApproveAllDraftChunksRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_EditDraftChunk_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EditDraftChunkRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).EditDraftChunk(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_EditDraftChunk_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).EditDraftChunk(ctx, req.(*EditDraftChunkRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_UndoDraftChunk_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UndoDraftChunkRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).UndoDraftChunk(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_UndoDraftChunk_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).UndoDraftChunk(ctx, req.(*UndoDraftChunkRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ClearDraftChunks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClearDraftChunksRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ClearDraftChunks(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ClearDraftChunks_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ClearDraftChunks(ctx, req.(*ClearDraftChunksRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetDraftHistory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDraftHistoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetDraftHistory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetDraftHistory_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetDraftHistory(ctx, req.(*GetDraftHistoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_IssueSandboxToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(IssueSandboxTokenRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).IssueSandboxToken(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_IssueSandboxToken_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).IssueSandboxToken(ctx, req.(*IssueSandboxTokenRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_RefreshSandboxToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RefreshSandboxTokenRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).RefreshSandboxToken(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_RefreshSandboxToken_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).RefreshSandboxToken(ctx, req.(*RefreshSandboxTokenRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_CreateWorkspace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateWorkspaceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).CreateWorkspace(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_CreateWorkspace_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).CreateWorkspace(ctx, req.(*CreateWorkspaceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetWorkspace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetWorkspaceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetWorkspace(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetWorkspace_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetWorkspace(ctx, req.(*GetWorkspaceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ListWorkspaces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListWorkspacesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ListWorkspaces(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ListWorkspaces_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ListWorkspaces(ctx, req.(*ListWorkspacesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_DeleteWorkspace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteWorkspaceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).DeleteWorkspace(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_DeleteWorkspace_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).DeleteWorkspace(ctx, req.(*DeleteWorkspaceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_AddWorkspaceMember_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddWorkspaceMemberRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).AddWorkspaceMember(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_AddWorkspaceMember_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).AddWorkspaceMember(ctx, req.(*AddWorkspaceMemberRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_RemoveWorkspaceMember_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RemoveWorkspaceMemberRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).RemoveWorkspaceMember(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_RemoveWorkspaceMember_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).RemoveWorkspaceMember(ctx, req.(*RemoveWorkspaceMemberRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ListWorkspaceMembers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListWorkspaceMembersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ListWorkspaceMembers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ListWorkspaceMembers_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ListWorkspaceMembers(ctx, req.(*ListWorkspaceMembersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// OpenShell_ServiceDesc is the grpc.ServiceDesc for OpenShell service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var OpenShell_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "openshell.v1.OpenShell", + HandlerType: (*OpenShellServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Health", + Handler: _OpenShell_Health_Handler, + }, + { + MethodName: "GetCurrentUser", + Handler: _OpenShell_GetCurrentUser_Handler, + }, + { + MethodName: "GetGatewayInfo", + Handler: _OpenShell_GetGatewayInfo_Handler, + }, + { + MethodName: "CreateSandbox", + Handler: _OpenShell_CreateSandbox_Handler, + }, + { + MethodName: "GetSandbox", + Handler: _OpenShell_GetSandbox_Handler, + }, + { + MethodName: "ListSandboxes", + Handler: _OpenShell_ListSandboxes_Handler, + }, + { + MethodName: "ListSandboxProviders", + Handler: _OpenShell_ListSandboxProviders_Handler, + }, + { + MethodName: "AttachSandboxProvider", + Handler: _OpenShell_AttachSandboxProvider_Handler, + }, + { + MethodName: "DetachSandboxProvider", + Handler: _OpenShell_DetachSandboxProvider_Handler, + }, + { + MethodName: "DeleteSandbox", + Handler: _OpenShell_DeleteSandbox_Handler, + }, + { + MethodName: "CreateSshSession", + Handler: _OpenShell_CreateSshSession_Handler, + }, + { + MethodName: "ExposeService", + Handler: _OpenShell_ExposeService_Handler, + }, + { + MethodName: "GetService", + Handler: _OpenShell_GetService_Handler, + }, + { + MethodName: "ListServices", + Handler: _OpenShell_ListServices_Handler, + }, + { + MethodName: "DeleteService", + Handler: _OpenShell_DeleteService_Handler, + }, + { + MethodName: "RevokeSshSession", + Handler: _OpenShell_RevokeSshSession_Handler, + }, + { + MethodName: "CreateProvider", + Handler: _OpenShell_CreateProvider_Handler, + }, + { + MethodName: "GetProvider", + Handler: _OpenShell_GetProvider_Handler, + }, + { + MethodName: "ListProviders", + Handler: _OpenShell_ListProviders_Handler, + }, + { + MethodName: "ListProviderProfiles", + Handler: _OpenShell_ListProviderProfiles_Handler, + }, + { + MethodName: "GetProviderProfile", + Handler: _OpenShell_GetProviderProfile_Handler, + }, + { + MethodName: "ImportProviderProfiles", + Handler: _OpenShell_ImportProviderProfiles_Handler, + }, + { + MethodName: "UpdateProviderProfiles", + Handler: _OpenShell_UpdateProviderProfiles_Handler, + }, + { + MethodName: "LintProviderProfiles", + Handler: _OpenShell_LintProviderProfiles_Handler, + }, + { + MethodName: "UpdateProvider", + Handler: _OpenShell_UpdateProvider_Handler, + }, + { + MethodName: "GetProviderRefreshStatus", + Handler: _OpenShell_GetProviderRefreshStatus_Handler, + }, + { + MethodName: "ConfigureProviderRefresh", + Handler: _OpenShell_ConfigureProviderRefresh_Handler, + }, + { + MethodName: "RotateProviderCredential", + Handler: _OpenShell_RotateProviderCredential_Handler, + }, + { + MethodName: "DeleteProviderRefresh", + Handler: _OpenShell_DeleteProviderRefresh_Handler, + }, + { + MethodName: "DeleteProvider", + Handler: _OpenShell_DeleteProvider_Handler, + }, + { + MethodName: "DeleteProviderProfile", + Handler: _OpenShell_DeleteProviderProfile_Handler, + }, + { + MethodName: "GetSandboxConfig", + Handler: _OpenShell_GetSandboxConfig_Handler, + }, + { + MethodName: "GetGatewayConfig", + Handler: _OpenShell_GetGatewayConfig_Handler, + }, + { + MethodName: "UpdateConfig", + Handler: _OpenShell_UpdateConfig_Handler, + }, + { + MethodName: "GetSandboxPolicyStatus", + Handler: _OpenShell_GetSandboxPolicyStatus_Handler, + }, + { + MethodName: "ListSandboxPolicies", + Handler: _OpenShell_ListSandboxPolicies_Handler, + }, + { + MethodName: "ReportPolicyStatus", + Handler: _OpenShell_ReportPolicyStatus_Handler, + }, + { + MethodName: "GetSandboxProviderEnvironment", + Handler: _OpenShell_GetSandboxProviderEnvironment_Handler, + }, + { + MethodName: "GetSandboxLogs", + Handler: _OpenShell_GetSandboxLogs_Handler, + }, + { + MethodName: "SubmitPolicyAnalysis", + Handler: _OpenShell_SubmitPolicyAnalysis_Handler, + }, + { + MethodName: "GetDraftPolicy", + Handler: _OpenShell_GetDraftPolicy_Handler, + }, + { + MethodName: "ApproveDraftChunk", + Handler: _OpenShell_ApproveDraftChunk_Handler, + }, + { + MethodName: "RejectDraftChunk", + Handler: _OpenShell_RejectDraftChunk_Handler, + }, + { + MethodName: "ApproveAllDraftChunks", + Handler: _OpenShell_ApproveAllDraftChunks_Handler, + }, + { + MethodName: "EditDraftChunk", + Handler: _OpenShell_EditDraftChunk_Handler, + }, + { + MethodName: "UndoDraftChunk", + Handler: _OpenShell_UndoDraftChunk_Handler, + }, + { + MethodName: "ClearDraftChunks", + Handler: _OpenShell_ClearDraftChunks_Handler, + }, + { + MethodName: "GetDraftHistory", + Handler: _OpenShell_GetDraftHistory_Handler, + }, + { + MethodName: "IssueSandboxToken", + Handler: _OpenShell_IssueSandboxToken_Handler, + }, + { + MethodName: "RefreshSandboxToken", + Handler: _OpenShell_RefreshSandboxToken_Handler, + }, + { + MethodName: "CreateWorkspace", + Handler: _OpenShell_CreateWorkspace_Handler, + }, + { + MethodName: "GetWorkspace", + Handler: _OpenShell_GetWorkspace_Handler, + }, + { + MethodName: "ListWorkspaces", + Handler: _OpenShell_ListWorkspaces_Handler, + }, + { + MethodName: "DeleteWorkspace", + Handler: _OpenShell_DeleteWorkspace_Handler, + }, + { + MethodName: "AddWorkspaceMember", + Handler: _OpenShell_AddWorkspaceMember_Handler, + }, + { + MethodName: "RemoveWorkspaceMember", + Handler: _OpenShell_RemoveWorkspaceMember_Handler, + }, + { + MethodName: "ListWorkspaceMembers", + Handler: _OpenShell_ListWorkspaceMembers_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "ExecSandbox", + Handler: _OpenShell_ExecSandbox_Handler, + ServerStreams: true, + }, + { + StreamName: "ForwardTcp", + Handler: _OpenShell_ForwardTcp_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "ExecSandboxInteractive", + Handler: _OpenShell_ExecSandboxInteractive_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "PushSandboxLogs", + Handler: _OpenShell_PushSandboxLogs_Handler, + ClientStreams: true, + }, + { + StreamName: "ConnectSupervisor", + Handler: _OpenShell_ConnectSupervisor_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "RelayStream", + Handler: _OpenShell_RelayStream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "WatchSandbox", + Handler: _OpenShell_WatchSandbox_Handler, + ServerStreams: true, + }, + }, + Metadata: "openshell.proto", +} diff --git a/sdk/go/proto/optionsv1/options.pb.go b/sdk/go/proto/optionsv1/options.pb.go new file mode 100644 index 000000000..3219a94b1 --- /dev/null +++ b/sdk/go/proto/optionsv1/options.pb.go @@ -0,0 +1,204 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: options.proto + +package optionsv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + descriptorpb "google.golang.org/protobuf/types/descriptorpb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Per-method authorization rule. Consumed at runtime by the gateway's +// descriptor-pool-based auth table to enforce auth mode, role, and scope. +type AuthorizationRule struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Authentication mode: "bearer", "sandbox", "dual", or "unauthenticated". + AuthMode string `protobuf:"bytes,1,opt,name=auth_mode,json=authMode,proto3" json:"auth_mode,omitempty"` + // Minimum workspace-level role required (checked by handler via + // authorize_workspace): "user" or "admin". Mutually exclusive with + // global_role. + WorkspaceRole string `protobuf:"bytes,2,opt,name=workspace_role,json=workspaceRole,proto3" json:"workspace_role,omitempty"` + // Global role required (checked by middleware via OIDC claims): + // "platform_admin". Mutually exclusive with workspace_role. + GlobalRole string `protobuf:"bytes,3,opt,name=global_role,json=globalRole,proto3" json:"global_role,omitempty"` + // Required OIDC scope on the bearer path (e.g. "sandbox:read"). + Scope string `protobuf:"bytes,4,opt,name=scope,proto3" json:"scope,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthorizationRule) Reset() { + *x = AuthorizationRule{} + mi := &file_options_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthorizationRule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthorizationRule) ProtoMessage() {} + +func (x *AuthorizationRule) ProtoReflect() protoreflect.Message { + mi := &file_options_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthorizationRule.ProtoReflect.Descriptor instead. +func (*AuthorizationRule) Descriptor() ([]byte, []int) { + return file_options_proto_rawDescGZIP(), []int{0} +} + +func (x *AuthorizationRule) GetAuthMode() string { + if x != nil { + return x.AuthMode + } + return "" +} + +func (x *AuthorizationRule) GetWorkspaceRole() string { + if x != nil { + return x.WorkspaceRole + } + return "" +} + +func (x *AuthorizationRule) GetGlobalRole() string { + if x != nil { + return x.GlobalRole + } + return "" +} + +func (x *AuthorizationRule) GetScope() string { + if x != nil { + return x.Scope + } + return "" +} + +var file_options_proto_extTypes = []protoimpl.ExtensionInfo{ + { + ExtendedType: (*descriptorpb.MethodOptions)(nil), + ExtensionType: (*AuthorizationRule)(nil), + Field: 50000, + Name: "openshell.options.v1.authorization", + Tag: "bytes,50000,opt,name=authorization", + Filename: "options.proto", + }, + { + ExtendedType: (*descriptorpb.FieldOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50001, + Name: "openshell.options.v1.secret", + Tag: "varint,50001,opt,name=secret", + Filename: "options.proto", + }, +} + +// Extension fields to descriptorpb.MethodOptions. +var ( + // Authorization metadata for a gRPC method. + // + // optional openshell.options.v1.AuthorizationRule authorization = 50000; + E_Authorization = &file_options_proto_extTypes[0] +) + +// Extension fields to descriptorpb.FieldOptions. +var ( + // optional bool secret = 50001; + E_Secret = &file_options_proto_extTypes[1] +) + +var File_options_proto protoreflect.FileDescriptor + +const file_options_proto_rawDesc = "" + + "\n" + + "\roptions.proto\x12\x14openshell.options.v1\x1a google/protobuf/descriptor.proto\"\x8e\x01\n" + + "\x11AuthorizationRule\x12\x1b\n" + + "\tauth_mode\x18\x01 \x01(\tR\bauthMode\x12%\n" + + "\x0eworkspace_role\x18\x02 \x01(\tR\rworkspaceRole\x12\x1f\n" + + "\vglobal_role\x18\x03 \x01(\tR\n" + + "globalRole\x12\x14\n" + + "\x05scope\x18\x04 \x01(\tR\x05scope:o\n" + + "\rauthorization\x12\x1e.google.protobuf.MethodOptions\x18І\x03 \x01(\v2'.openshell.options.v1.AuthorizationRuleR\rauthorization:7\n" + + "\x06secret\x12\x1d.google.protobuf.FieldOptions\x18ц\x03 \x01(\bR\x06secretb\x06proto3" + +var ( + file_options_proto_rawDescOnce sync.Once + file_options_proto_rawDescData []byte +) + +func file_options_proto_rawDescGZIP() []byte { + file_options_proto_rawDescOnce.Do(func() { + file_options_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_options_proto_rawDesc), len(file_options_proto_rawDesc))) + }) + return file_options_proto_rawDescData +} + +var file_options_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_options_proto_goTypes = []any{ + (*AuthorizationRule)(nil), // 0: openshell.options.v1.AuthorizationRule + (*descriptorpb.MethodOptions)(nil), // 1: google.protobuf.MethodOptions + (*descriptorpb.FieldOptions)(nil), // 2: google.protobuf.FieldOptions +} +var file_options_proto_depIdxs = []int32{ + 1, // 0: openshell.options.v1.authorization:extendee -> google.protobuf.MethodOptions + 2, // 1: openshell.options.v1.secret:extendee -> google.protobuf.FieldOptions + 0, // 2: openshell.options.v1.authorization:type_name -> openshell.options.v1.AuthorizationRule + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 2, // [2:3] is the sub-list for extension type_name + 0, // [0:2] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_options_proto_init() } +func file_options_proto_init() { + if File_options_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_options_proto_rawDesc), len(file_options_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 2, + NumServices: 0, + }, + GoTypes: file_options_proto_goTypes, + DependencyIndexes: file_options_proto_depIdxs, + MessageInfos: file_options_proto_msgTypes, + ExtensionInfos: file_options_proto_extTypes, + }.Build() + File_options_proto = out.File + file_options_proto_goTypes = nil + file_options_proto_depIdxs = nil +} diff --git a/sdk/go/proto/sandboxv1/sandbox.pb.go b/sdk/go/proto/sandboxv1/sandbox.pb.go new file mode 100644 index 000000000..6ed4cf2ec --- /dev/null +++ b/sdk/go/proto/sandboxv1/sandbox.pb.go @@ -0,0 +1,2234 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: sandbox.proto + +package sandboxv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Scope that currently controls a setting. +type SettingScope int32 + +const ( + SettingScope_SETTING_SCOPE_UNSPECIFIED SettingScope = 0 + SettingScope_SETTING_SCOPE_SANDBOX SettingScope = 1 + SettingScope_SETTING_SCOPE_GLOBAL SettingScope = 2 +) + +// Enum value maps for SettingScope. +var ( + SettingScope_name = map[int32]string{ + 0: "SETTING_SCOPE_UNSPECIFIED", + 1: "SETTING_SCOPE_SANDBOX", + 2: "SETTING_SCOPE_GLOBAL", + } + SettingScope_value = map[string]int32{ + "SETTING_SCOPE_UNSPECIFIED": 0, + "SETTING_SCOPE_SANDBOX": 1, + "SETTING_SCOPE_GLOBAL": 2, + } +) + +func (x SettingScope) Enum() *SettingScope { + p := new(SettingScope) + *p = x + return p +} + +func (x SettingScope) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SettingScope) Descriptor() protoreflect.EnumDescriptor { + return file_sandbox_proto_enumTypes[0].Descriptor() +} + +func (SettingScope) Type() protoreflect.EnumType { + return &file_sandbox_proto_enumTypes[0] +} + +func (x SettingScope) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SettingScope.Descriptor instead. +func (SettingScope) EnumDescriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{0} +} + +// Source used for the policy payload in GetSandboxConfigResponse. +type PolicySource int32 + +const ( + PolicySource_POLICY_SOURCE_UNSPECIFIED PolicySource = 0 + PolicySource_POLICY_SOURCE_SANDBOX PolicySource = 1 + PolicySource_POLICY_SOURCE_GLOBAL PolicySource = 2 +) + +// Enum value maps for PolicySource. +var ( + PolicySource_name = map[int32]string{ + 0: "POLICY_SOURCE_UNSPECIFIED", + 1: "POLICY_SOURCE_SANDBOX", + 2: "POLICY_SOURCE_GLOBAL", + } + PolicySource_value = map[string]int32{ + "POLICY_SOURCE_UNSPECIFIED": 0, + "POLICY_SOURCE_SANDBOX": 1, + "POLICY_SOURCE_GLOBAL": 2, + } +) + +func (x PolicySource) Enum() *PolicySource { + p := new(PolicySource) + *p = x + return p +} + +func (x PolicySource) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PolicySource) Descriptor() protoreflect.EnumDescriptor { + return file_sandbox_proto_enumTypes[1].Descriptor() +} + +func (PolicySource) Type() protoreflect.EnumType { + return &file_sandbox_proto_enumTypes[1] +} + +func (x PolicySource) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PolicySource.Descriptor instead. +func (PolicySource) EnumDescriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{1} +} + +// Sandbox security policy configuration. +type SandboxPolicy struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Policy version. + Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + // Filesystem access policy. + Filesystem *FilesystemPolicy `protobuf:"bytes,2,opt,name=filesystem,proto3" json:"filesystem,omitempty"` + // Landlock configuration. + Landlock *LandlockPolicy `protobuf:"bytes,3,opt,name=landlock,proto3" json:"landlock,omitempty"` + // Process execution policy. + Process *ProcessPolicy `protobuf:"bytes,4,opt,name=process,proto3" json:"process,omitempty"` + // Network access policies keyed by name (e.g. "claude_code", "gitlab"). + NetworkPolicies map[string]*NetworkPolicyRule `protobuf:"bytes,5,rep,name=network_policies,json=networkPolicies,proto3" json:"network_policies,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Reusable supervisor middleware configs for network egress, keyed by their + // policy-local names. At most 10 configs are accepted, and at most 10 stages + // can be selected per request. + NetworkMiddlewares map[string]*NetworkMiddlewareConfig `protobuf:"bytes,6,rep,name=network_middlewares,json=networkMiddlewares,proto3" json:"network_middlewares,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxPolicy) Reset() { + *x = SandboxPolicy{} + mi := &file_sandbox_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxPolicy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxPolicy) ProtoMessage() {} + +func (x *SandboxPolicy) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxPolicy.ProtoReflect.Descriptor instead. +func (*SandboxPolicy) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{0} +} + +func (x *SandboxPolicy) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *SandboxPolicy) GetFilesystem() *FilesystemPolicy { + if x != nil { + return x.Filesystem + } + return nil +} + +func (x *SandboxPolicy) GetLandlock() *LandlockPolicy { + if x != nil { + return x.Landlock + } + return nil +} + +func (x *SandboxPolicy) GetProcess() *ProcessPolicy { + if x != nil { + return x.Process + } + return nil +} + +func (x *SandboxPolicy) GetNetworkPolicies() map[string]*NetworkPolicyRule { + if x != nil { + return x.NetworkPolicies + } + return nil +} + +func (x *SandboxPolicy) GetNetworkMiddlewares() map[string]*NetworkMiddlewareConfig { + if x != nil { + return x.NetworkMiddlewares + } + return nil +} + +// Filesystem access policy. +type FilesystemPolicy struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Automatically include the workdir as read-write. + IncludeWorkdir bool `protobuf:"varint,1,opt,name=include_workdir,json=includeWorkdir,proto3" json:"include_workdir,omitempty"` + // Read-only directory allow list. + ReadOnly []string `protobuf:"bytes,2,rep,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"` + // Read-write directory allow list. + ReadWrite []string `protobuf:"bytes,3,rep,name=read_write,json=readWrite,proto3" json:"read_write,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FilesystemPolicy) Reset() { + *x = FilesystemPolicy{} + mi := &file_sandbox_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FilesystemPolicy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FilesystemPolicy) ProtoMessage() {} + +func (x *FilesystemPolicy) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FilesystemPolicy.ProtoReflect.Descriptor instead. +func (*FilesystemPolicy) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{1} +} + +func (x *FilesystemPolicy) GetIncludeWorkdir() bool { + if x != nil { + return x.IncludeWorkdir + } + return false +} + +func (x *FilesystemPolicy) GetReadOnly() []string { + if x != nil { + return x.ReadOnly + } + return nil +} + +func (x *FilesystemPolicy) GetReadWrite() []string { + if x != nil { + return x.ReadWrite + } + return nil +} + +// Landlock policy configuration. +type LandlockPolicy struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Compatibility mode (e.g. "best_effort", "hard_requirement"). + Compatibility string `protobuf:"bytes,1,opt,name=compatibility,proto3" json:"compatibility,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LandlockPolicy) Reset() { + *x = LandlockPolicy{} + mi := &file_sandbox_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LandlockPolicy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LandlockPolicy) ProtoMessage() {} + +func (x *LandlockPolicy) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LandlockPolicy.ProtoReflect.Descriptor instead. +func (*LandlockPolicy) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{2} +} + +func (x *LandlockPolicy) GetCompatibility() string { + if x != nil { + return x.Compatibility + } + return "" +} + +// Process execution policy. +type ProcessPolicy struct { + state protoimpl.MessageState `protogen:"open.v1"` + // User name to run the sandboxed process as. + RunAsUser string `protobuf:"bytes,1,opt,name=run_as_user,json=runAsUser,proto3" json:"run_as_user,omitempty"` + // Group name to run the sandboxed process as. + RunAsGroup string `protobuf:"bytes,2,opt,name=run_as_group,json=runAsGroup,proto3" json:"run_as_group,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProcessPolicy) Reset() { + *x = ProcessPolicy{} + mi := &file_sandbox_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProcessPolicy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProcessPolicy) ProtoMessage() {} + +func (x *ProcessPolicy) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProcessPolicy.ProtoReflect.Descriptor instead. +func (*ProcessPolicy) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{3} +} + +func (x *ProcessPolicy) GetRunAsUser() string { + if x != nil { + return x.RunAsUser + } + return "" +} + +func (x *ProcessPolicy) GetRunAsGroup() string { + if x != nil { + return x.RunAsGroup + } + return "" +} + +// A named network access policy rule. +type NetworkPolicyRule struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Human-readable name for this policy rule. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Allowed endpoint (host:port) pairs. + Endpoints []*NetworkEndpoint `protobuf:"bytes,2,rep,name=endpoints,proto3" json:"endpoints,omitempty"` + // Allowed binary identities. + Binaries []*NetworkBinary `protobuf:"bytes,3,rep,name=binaries,proto3" json:"binaries,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkPolicyRule) Reset() { + *x = NetworkPolicyRule{} + mi := &file_sandbox_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkPolicyRule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkPolicyRule) ProtoMessage() {} + +func (x *NetworkPolicyRule) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkPolicyRule.ProtoReflect.Descriptor instead. +func (*NetworkPolicyRule) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{4} +} + +func (x *NetworkPolicyRule) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *NetworkPolicyRule) GetEndpoints() []*NetworkEndpoint { + if x != nil { + return x.Endpoints + } + return nil +} + +func (x *NetworkPolicyRule) GetBinaries() []*NetworkBinary { + if x != nil { + return x.Binaries + } + return nil +} + +// A reusable middleware config selected for admitted egress by host. +type NetworkMiddlewareConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Human-readable name for this middleware config. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Built-in middleware name or operator-owned registration name. + Middleware string `protobuf:"bytes,2,opt,name=middleware,proto3" json:"middleware,omitempty"` + // Service-specific configuration. + Config *structpb.Struct `protobuf:"bytes,3,opt,name=config,proto3" json:"config,omitempty"` + // Failure behavior: "fail_closed" (default) or "fail_open". + OnError string `protobuf:"bytes,4,opt,name=on_error,json=onError,proto3" json:"on_error,omitempty"` + // Host selector controlling which admitted destinations use this config. + Endpoints *MiddlewareEndpointSelector `protobuf:"bytes,5,opt,name=endpoints,proto3" json:"endpoints,omitempty"` + // Execution order. Values must be unique within a policy; lower values run first. + Order int32 `protobuf:"varint,6,opt,name=order,proto3" json:"order,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkMiddlewareConfig) Reset() { + *x = NetworkMiddlewareConfig{} + mi := &file_sandbox_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkMiddlewareConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkMiddlewareConfig) ProtoMessage() {} + +func (x *NetworkMiddlewareConfig) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkMiddlewareConfig.ProtoReflect.Descriptor instead. +func (*NetworkMiddlewareConfig) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{5} +} + +func (x *NetworkMiddlewareConfig) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *NetworkMiddlewareConfig) GetMiddleware() string { + if x != nil { + return x.Middleware + } + return "" +} + +func (x *NetworkMiddlewareConfig) GetConfig() *structpb.Struct { + if x != nil { + return x.Config + } + return nil +} + +func (x *NetworkMiddlewareConfig) GetOnError() string { + if x != nil { + return x.OnError + } + return "" +} + +func (x *NetworkMiddlewareConfig) GetEndpoints() *MiddlewareEndpointSelector { + if x != nil { + return x.Endpoints + } + return nil +} + +func (x *NetworkMiddlewareConfig) GetOrder() int32 { + if x != nil { + return x.Order + } + return 0 +} + +// Host selector controlling which admitted destinations use a middleware config. +type MiddlewareEndpointSelector struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Exact host or DNS glob patterns included in the selection. Include and + // exclude accept at most 32 combined patterns. + Include []string `protobuf:"bytes,1,rep,name=include,proto3" json:"include,omitempty"` + // Exact host or DNS glob patterns removed from the selection. + // Exclusions take precedence over inclusions. + Exclude []string `protobuf:"bytes,2,rep,name=exclude,proto3" json:"exclude,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MiddlewareEndpointSelector) Reset() { + *x = MiddlewareEndpointSelector{} + mi := &file_sandbox_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MiddlewareEndpointSelector) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MiddlewareEndpointSelector) ProtoMessage() {} + +func (x *MiddlewareEndpointSelector) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MiddlewareEndpointSelector.ProtoReflect.Descriptor instead. +func (*MiddlewareEndpointSelector) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{6} +} + +func (x *MiddlewareEndpointSelector) GetInclude() []string { + if x != nil { + return x.Include + } + return nil +} + +func (x *MiddlewareEndpointSelector) GetExclude() []string { + if x != nil { + return x.Exclude + } + return nil +} + +// A network endpoint (host + port) with optional L7 inspection config. +type NetworkEndpoint struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Hostname or host glob pattern. Exact match is case-insensitive. + // Glob patterns use "." as delimiter: "*.example.com" matches a single + // subdomain label, "**.example.com" matches across labels. + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + // Single port (backwards compat). Use `ports` for multiple ports. + // Mutually exclusive with `ports` — if both are set, `ports` takes precedence. + Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + // Application protocol for L7 inspection: "rest", "websocket", "graphql", "sql", or "" (L4-only). + Protocol string `protobuf:"bytes,3,opt,name=protocol,proto3" json:"protocol,omitempty"` + // TLS handling: "terminate" or "passthrough" (default). + Tls string `protobuf:"bytes,4,opt,name=tls,proto3" json:"tls,omitempty"` + // Enforcement mode: "enforce" or "audit" (default). + Enforcement string `protobuf:"bytes,5,opt,name=enforcement,proto3" json:"enforcement,omitempty"` + // Access preset shorthand: "read-only", "read-write", "full". + // Mutually exclusive with rules. + Access string `protobuf:"bytes,6,opt,name=access,proto3" json:"access,omitempty"` + // Explicit L7 rules (mutually exclusive with access). + Rules []*L7Rule `protobuf:"bytes,7,rep,name=rules,proto3" json:"rules,omitempty"` + // Allowed resolved IP addresses or CIDR ranges for this endpoint. + // When non-empty, the SSRF internal-IP check is replaced by an allowlist check: + // - If host is also set: domain must resolve to an IP in this list. + // - If host is empty: any domain is allowed as long as it resolves to an IP in this list. + // + // Supports exact IPs ("10.0.5.20") and CIDR notation ("10.0.5.0/24"). + // Loopback (127.0.0.0/8) and link-local (169.254.0.0/16) are always blocked + // regardless of this field. + AllowedIps []string `protobuf:"bytes,8,rep,name=allowed_ips,json=allowedIps,proto3" json:"allowed_ips,omitempty"` + // Multiple ports. When non-empty, this endpoint covers all listed ports. + // If `port` is set and `ports` is empty, `port` is normalized to `ports: [port]`. + // If both are set, `ports` takes precedence. + Ports []uint32 `protobuf:"varint,9,rep,packed,name=ports,proto3" json:"ports,omitempty"` + // Explicit L7 deny rules. When present, requests matching any deny rule + // are blocked even if they match an allow rule or access preset. + // Deny rules take precedence over allow rules. + DenyRules []*L7DenyRule `protobuf:"bytes,10,rep,name=deny_rules,json=denyRules,proto3" json:"deny_rules,omitempty"` + // When true, percent-encoded '/' (%2F) is preserved in path segments + // rather than rejected by the L7 path canonicalizer. Required for + // upstreams like GitLab that embed %2F in namespaced resource paths. + // Defaults to false (strict). + AllowEncodedSlash bool `protobuf:"varint,11,opt,name=allow_encoded_slash,json=allowEncodedSlash,proto3" json:"allow_encoded_slash,omitempty"` + // GraphQL persisted-query behavior for hash-only/saved-query requests: + // "deny" (default) or "allow_registered". + PersistedQueries string `protobuf:"bytes,12,opt,name=persisted_queries,json=persistedQueries,proto3" json:"persisted_queries,omitempty"` + // Trusted GraphQL persisted-query registry keyed by hash or service-specific ID. + // Only used when persisted_queries is "allow_registered". + GraphqlPersistedQueries map[string]*GraphqlOperation `protobuf:"bytes,13,rep,name=graphql_persisted_queries,json=graphqlPersistedQueries,proto3" json:"graphql_persisted_queries,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Maximum GraphQL request body bytes to buffer for inspection. + // Defaults to 65536 when unset. + GraphqlMaxBodyBytes uint32 `protobuf:"varint,14,opt,name=graphql_max_body_bytes,json=graphqlMaxBodyBytes,proto3" json:"graphql_max_body_bytes,omitempty"` + // Optional HTTP path glob that scopes this L7 endpoint on shared host:port APIs. + // Example: use path "/graphql" for protocol "graphql" and "/repos/**" for + // protocol "rest" when both surfaces live under api.example.com:443. + // Empty means all paths. + Path string `protobuf:"bytes,15,opt,name=path,proto3" json:"path,omitempty"` + // When true on a "rest" endpoint, OpenShell rewrites credential placeholders + // inside client-to-server WebSocket text messages after an allowed HTTP 101 + // upgrade. Defaults to false. + WebsocketCredentialRewrite bool `protobuf:"varint,16,opt,name=websocket_credential_rewrite,json=websocketCredentialRewrite,proto3" json:"websocket_credential_rewrite,omitempty"` + // When true on a "rest" endpoint, OpenShell rewrites credential placeholders + // inside supported textual HTTP request bodies before forwarding upstream. + // Defaults to false. + RequestBodyCredentialRewrite bool `protobuf:"varint,17,opt,name=request_body_credential_rewrite,json=requestBodyCredentialRewrite,proto3" json:"request_body_credential_rewrite,omitempty"` + // Internal provenance marker for policy-advisor generated endpoints. + // Advisor-proposed endpoints must not satisfy exact-host SSRF trust unless + // they are converted through an explicit user-authored policy path. + AdvisorProposed bool `protobuf:"varint,18,opt,name=advisor_proposed,json=advisorProposed,proto3" json:"advisor_proposed,omitempty"` + // Proxy-side credential signing mode: "sigv4" for AWS SigV4 re-signing. + // When set, the proxy strips the client's Authorization header and computes + // a fresh SigV4 signature using real credentials from the provider. + CredentialSigning string `protobuf:"bytes,19,opt,name=credential_signing,json=credentialSigning,proto3" json:"credential_signing,omitempty"` + // AWS signing service name override. Required when credential_signing is + // "sigv4" — e.g. "bedrock" for bedrock-runtime endpoints. + SigningService string `protobuf:"bytes,20,opt,name=signing_service,json=signingService,proto3" json:"signing_service,omitempty"` + // AWS region override for SigV4 signing. When set, takes precedence over + // hostname-based region extraction. Required for non-standard endpoints. + SigningRegion string `protobuf:"bytes,21,opt,name=signing_region,json=signingRegion,proto3" json:"signing_region,omitempty"` + // Maximum JSON-RPC-over-HTTP request body bytes to buffer for inspection. + // Defaults to 65536 when unset. + JsonRpcMaxBodyBytes uint32 `protobuf:"varint,22,opt,name=json_rpc_max_body_bytes,json=jsonRpcMaxBodyBytes,proto3" json:"json_rpc_max_body_bytes,omitempty"` + // MCP-only policy and inspection options. Only used when protocol is "mcp". + Mcp *McpOptions `protobuf:"bytes,23,opt,name=mcp,proto3" json:"mcp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkEndpoint) Reset() { + *x = NetworkEndpoint{} + mi := &file_sandbox_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkEndpoint) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkEndpoint) ProtoMessage() {} + +func (x *NetworkEndpoint) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkEndpoint.ProtoReflect.Descriptor instead. +func (*NetworkEndpoint) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{7} +} + +func (x *NetworkEndpoint) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *NetworkEndpoint) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *NetworkEndpoint) GetProtocol() string { + if x != nil { + return x.Protocol + } + return "" +} + +func (x *NetworkEndpoint) GetTls() string { + if x != nil { + return x.Tls + } + return "" +} + +func (x *NetworkEndpoint) GetEnforcement() string { + if x != nil { + return x.Enforcement + } + return "" +} + +func (x *NetworkEndpoint) GetAccess() string { + if x != nil { + return x.Access + } + return "" +} + +func (x *NetworkEndpoint) GetRules() []*L7Rule { + if x != nil { + return x.Rules + } + return nil +} + +func (x *NetworkEndpoint) GetAllowedIps() []string { + if x != nil { + return x.AllowedIps + } + return nil +} + +func (x *NetworkEndpoint) GetPorts() []uint32 { + if x != nil { + return x.Ports + } + return nil +} + +func (x *NetworkEndpoint) GetDenyRules() []*L7DenyRule { + if x != nil { + return x.DenyRules + } + return nil +} + +func (x *NetworkEndpoint) GetAllowEncodedSlash() bool { + if x != nil { + return x.AllowEncodedSlash + } + return false +} + +func (x *NetworkEndpoint) GetPersistedQueries() string { + if x != nil { + return x.PersistedQueries + } + return "" +} + +func (x *NetworkEndpoint) GetGraphqlPersistedQueries() map[string]*GraphqlOperation { + if x != nil { + return x.GraphqlPersistedQueries + } + return nil +} + +func (x *NetworkEndpoint) GetGraphqlMaxBodyBytes() uint32 { + if x != nil { + return x.GraphqlMaxBodyBytes + } + return 0 +} + +func (x *NetworkEndpoint) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *NetworkEndpoint) GetWebsocketCredentialRewrite() bool { + if x != nil { + return x.WebsocketCredentialRewrite + } + return false +} + +func (x *NetworkEndpoint) GetRequestBodyCredentialRewrite() bool { + if x != nil { + return x.RequestBodyCredentialRewrite + } + return false +} + +func (x *NetworkEndpoint) GetAdvisorProposed() bool { + if x != nil { + return x.AdvisorProposed + } + return false +} + +func (x *NetworkEndpoint) GetCredentialSigning() string { + if x != nil { + return x.CredentialSigning + } + return "" +} + +func (x *NetworkEndpoint) GetSigningService() string { + if x != nil { + return x.SigningService + } + return "" +} + +func (x *NetworkEndpoint) GetSigningRegion() string { + if x != nil { + return x.SigningRegion + } + return "" +} + +func (x *NetworkEndpoint) GetJsonRpcMaxBodyBytes() uint32 { + if x != nil { + return x.JsonRpcMaxBodyBytes + } + return 0 +} + +func (x *NetworkEndpoint) GetMcp() *McpOptions { + if x != nil { + return x.Mcp + } + return nil +} + +// MCP options are grouped so MCP-specific policy can grow without adding more +// top-level NetworkEndpoint fields. Current enforcement targets the active +// 2025-11-25 Streamable HTTP/tools behavior, while preserving space for +// version-profile policy if OpenShell adopts 2026-07-28 draft behavior later. +// +// Planned policy extensions should use OpenShell-owned static definitions for +// MCP method/version profiles rather than treating dependency enums as the +// policy contract. Candidate profile checks include request metadata/header +// validation, response/SSE introspection, trusted annotation handling, +// resultType/cache metadata validation, x-mcp-header tool-definition checks, +// and subscriptions/listen handling. +// +// Sources: +// - https://modelcontextprotocol.io/specification/2025-11-25/server/tools +// - https://modelcontextprotocol.io/specification/draft/changelog +// - https://modelcontextprotocol.io/specification/draft/basic/transports/streamable-http +// - https://modelcontextprotocol.io/specification/draft/server/tools +type McpOptions struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Hardening boundary for tools/call params.name. When unset or true, the + // supervisor enforces the MCP recommended tool-name syntax + // ^[A-Za-z0-9_.-]{1,128}$ before policy evaluation. Set false only for + // compatibility with servers that intentionally use non-recommended names. + // + // Source: + // - https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-names + StrictToolNames *bool `protobuf:"varint,1,opt,name=strict_tool_names,json=strictToolNames,proto3,oneof" json:"strict_tool_names,omitempty"` + // Method-layer default for MCP endpoints. When true, OpenShell allows parsed + // MCP-family methods at the method layer unless a tool-name policy narrows + // tools/call. When unset or false, explicit method rules are required. + AllowAllKnownMcpMethods *bool `protobuf:"varint,2,opt,name=allow_all_known_mcp_methods,json=allowAllKnownMcpMethods,proto3,oneof" json:"allow_all_known_mcp_methods,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *McpOptions) Reset() { + *x = McpOptions{} + mi := &file_sandbox_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *McpOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*McpOptions) ProtoMessage() {} + +func (x *McpOptions) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use McpOptions.ProtoReflect.Descriptor instead. +func (*McpOptions) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{8} +} + +func (x *McpOptions) GetStrictToolNames() bool { + if x != nil && x.StrictToolNames != nil { + return *x.StrictToolNames + } + return false +} + +func (x *McpOptions) GetAllowAllKnownMcpMethods() bool { + if x != nil && x.AllowAllKnownMcpMethods != nil { + return *x.AllowAllKnownMcpMethods + } + return false +} + +// Trusted GraphQL operation classification. +type GraphqlOperation struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Operation type: "query", "mutation", or "subscription". + OperationType string `protobuf:"bytes,1,opt,name=operation_type,json=operationType,proto3" json:"operation_type,omitempty"` + // Operation name, if known. + OperationName string `protobuf:"bytes,2,opt,name=operation_name,json=operationName,proto3" json:"operation_name,omitempty"` + // Root field names selected by the operation. + Fields []string `protobuf:"bytes,3,rep,name=fields,proto3" json:"fields,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GraphqlOperation) Reset() { + *x = GraphqlOperation{} + mi := &file_sandbox_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GraphqlOperation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GraphqlOperation) ProtoMessage() {} + +func (x *GraphqlOperation) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GraphqlOperation.ProtoReflect.Descriptor instead. +func (*GraphqlOperation) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{9} +} + +func (x *GraphqlOperation) GetOperationType() string { + if x != nil { + return x.OperationType + } + return "" +} + +func (x *GraphqlOperation) GetOperationName() string { + if x != nil { + return x.OperationName + } + return "" +} + +func (x *GraphqlOperation) GetFields() []string { + if x != nil { + return x.Fields + } + return nil +} + +// An L7 deny rule that blocks specific requests. +// Mirrors L7Allow — same fields, same matching semantics, inverted effect. +// Deny rules are evaluated after allow rules and take precedence. +type L7DenyRule struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Protocol method: HTTP method (REST/WebSocket), JSON-RPC method name, or + // "*" for any when supported by the protocol. + Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` + // URL path glob pattern (REST): "/repos/*/pulls/*/reviews", "**" for any. + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + // SQL command (SQL): SELECT, INSERT, etc. or "*" for any. + Command string `protobuf:"bytes,3,opt,name=command,proto3" json:"command,omitempty"` + // Query parameter matcher map (REST). + // Same semantics as L7Allow.query. + Query map[string]*L7QueryMatcher `protobuf:"bytes,4,rep,name=query,proto3" json:"query,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // GraphQL operation type: "query", "mutation", "subscription", or "*" for any. + OperationType string `protobuf:"bytes,5,opt,name=operation_type,json=operationType,proto3" json:"operation_type,omitempty"` + // GraphQL operation name glob. "*" matches any operation name. + OperationName string `protobuf:"bytes,6,opt,name=operation_name,json=operationName,proto3" json:"operation_name,omitempty"` + // GraphQL root field globs. Deny rules match when any selected root field + // matches any configured glob. + Fields []string `protobuf:"bytes,7,rep,name=fields,proto3" json:"fields,omitempty"` + // MCP params matcher map. Currently only params.name is supported for + // tools/call filtering. Generic protocol "json-rpc" rejects params matchers. + Params map[string]*L7QueryMatcher `protobuf:"bytes,9,rep,name=params,proto3" json:"params,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *L7DenyRule) Reset() { + *x = L7DenyRule{} + mi := &file_sandbox_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *L7DenyRule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*L7DenyRule) ProtoMessage() {} + +func (x *L7DenyRule) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use L7DenyRule.ProtoReflect.Descriptor instead. +func (*L7DenyRule) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{10} +} + +func (x *L7DenyRule) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *L7DenyRule) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *L7DenyRule) GetCommand() string { + if x != nil { + return x.Command + } + return "" +} + +func (x *L7DenyRule) GetQuery() map[string]*L7QueryMatcher { + if x != nil { + return x.Query + } + return nil +} + +func (x *L7DenyRule) GetOperationType() string { + if x != nil { + return x.OperationType + } + return "" +} + +func (x *L7DenyRule) GetOperationName() string { + if x != nil { + return x.OperationName + } + return "" +} + +func (x *L7DenyRule) GetFields() []string { + if x != nil { + return x.Fields + } + return nil +} + +func (x *L7DenyRule) GetParams() map[string]*L7QueryMatcher { + if x != nil { + return x.Params + } + return nil +} + +// An L7 policy rule (allow-only). +type L7Rule struct { + state protoimpl.MessageState `protogen:"open.v1"` + Allow *L7Allow `protobuf:"bytes,1,opt,name=allow,proto3" json:"allow,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *L7Rule) Reset() { + *x = L7Rule{} + mi := &file_sandbox_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *L7Rule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*L7Rule) ProtoMessage() {} + +func (x *L7Rule) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use L7Rule.ProtoReflect.Descriptor instead. +func (*L7Rule) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{11} +} + +func (x *L7Rule) GetAllow() *L7Allow { + if x != nil { + return x.Allow + } + return nil +} + +// Allowed action definition for L7 rules. +type L7Allow struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Protocol method: HTTP method (REST/WebSocket), JSON-RPC method name, or + // "*" for any when supported by the protocol. + Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` + // URL path glob pattern (REST): "/repos/**", "**" for any. + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + // SQL command (SQL): SELECT, INSERT, etc. or "*" for any. + Command string `protobuf:"bytes,3,opt,name=command,proto3" json:"command,omitempty"` + // Query parameter matcher map (REST). + // Key is the decoded query parameter name (case-sensitive). + // Value supports either a single glob (`glob`) or a list (`any`). + Query map[string]*L7QueryMatcher `protobuf:"bytes,4,rep,name=query,proto3" json:"query,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // GraphQL operation type: "query", "mutation", "subscription", or "*" for any. + OperationType string `protobuf:"bytes,5,opt,name=operation_type,json=operationType,proto3" json:"operation_type,omitempty"` + // GraphQL operation name glob. "*" matches any operation name. + OperationName string `protobuf:"bytes,6,opt,name=operation_name,json=operationName,proto3" json:"operation_name,omitempty"` + // GraphQL root field globs. Allow rules match only when every selected root + // field matches one of the configured globs. Omit to match all fields. + Fields []string `protobuf:"bytes,7,rep,name=fields,proto3" json:"fields,omitempty"` + // MCP params matcher map. Currently only params.name is supported for + // tools/call filtering. Generic protocol "json-rpc" rejects params matchers. + Params map[string]*L7QueryMatcher `protobuf:"bytes,9,rep,name=params,proto3" json:"params,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *L7Allow) Reset() { + *x = L7Allow{} + mi := &file_sandbox_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *L7Allow) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*L7Allow) ProtoMessage() {} + +func (x *L7Allow) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use L7Allow.ProtoReflect.Descriptor instead. +func (*L7Allow) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{12} +} + +func (x *L7Allow) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *L7Allow) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *L7Allow) GetCommand() string { + if x != nil { + return x.Command + } + return "" +} + +func (x *L7Allow) GetQuery() map[string]*L7QueryMatcher { + if x != nil { + return x.Query + } + return nil +} + +func (x *L7Allow) GetOperationType() string { + if x != nil { + return x.OperationType + } + return "" +} + +func (x *L7Allow) GetOperationName() string { + if x != nil { + return x.OperationName + } + return "" +} + +func (x *L7Allow) GetFields() []string { + if x != nil { + return x.Fields + } + return nil +} + +func (x *L7Allow) GetParams() map[string]*L7QueryMatcher { + if x != nil { + return x.Params + } + return nil +} + +// Query value matcher for one query parameter key. +type L7QueryMatcher struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Single glob pattern. + Glob string `protobuf:"bytes,1,opt,name=glob,proto3" json:"glob,omitempty"` + // Any-of glob patterns. + Any []string `protobuf:"bytes,2,rep,name=any,proto3" json:"any,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *L7QueryMatcher) Reset() { + *x = L7QueryMatcher{} + mi := &file_sandbox_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *L7QueryMatcher) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*L7QueryMatcher) ProtoMessage() {} + +func (x *L7QueryMatcher) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use L7QueryMatcher.ProtoReflect.Descriptor instead. +func (*L7QueryMatcher) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{13} +} + +func (x *L7QueryMatcher) GetGlob() string { + if x != nil { + return x.Glob + } + return "" +} + +func (x *L7QueryMatcher) GetAny() []string { + if x != nil { + return x.Any + } + return nil +} + +// A binary identity for network policy matching. +type NetworkBinary struct { + state protoimpl.MessageState `protogen:"open.v1"` + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + // Deprecated: the harness concept has been removed. This field is ignored. + // + // Deprecated: Marked as deprecated in sandbox.proto. + Harness bool `protobuf:"varint,2,opt,name=harness,proto3" json:"harness,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkBinary) Reset() { + *x = NetworkBinary{} + mi := &file_sandbox_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkBinary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkBinary) ProtoMessage() {} + +func (x *NetworkBinary) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkBinary.ProtoReflect.Descriptor instead. +func (*NetworkBinary) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{14} +} + +func (x *NetworkBinary) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +// Deprecated: Marked as deprecated in sandbox.proto. +func (x *NetworkBinary) GetHarness() bool { + if x != nil { + return x.Harness + } + return false +} + +// Request to get sandbox settings by sandbox ID. +type GetSandboxConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The sandbox ID. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSandboxConfigRequest) Reset() { + *x = GetSandboxConfigRequest{} + mi := &file_sandbox_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSandboxConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSandboxConfigRequest) ProtoMessage() {} + +func (x *GetSandboxConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSandboxConfigRequest.ProtoReflect.Descriptor instead. +func (*GetSandboxConfigRequest) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{15} +} + +func (x *GetSandboxConfigRequest) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +// Request to get gateway-global settings. +type GetGatewayConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetGatewayConfigRequest) Reset() { + *x = GetGatewayConfigRequest{} + mi := &file_sandbox_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetGatewayConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetGatewayConfigRequest) ProtoMessage() {} + +func (x *GetGatewayConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetGatewayConfigRequest.ProtoReflect.Descriptor instead. +func (*GetGatewayConfigRequest) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{16} +} + +// Response containing gateway-global settings. +type GetGatewayConfigResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Gateway-global settings map excluding the reserved policy key. + // Registered keys without a configured value are returned with an empty SettingValue. + Settings map[string]*SettingValue `protobuf:"bytes,1,rep,name=settings,proto3" json:"settings,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Monotonically increasing revision for gateway-global settings. + SettingsRevision uint64 `protobuf:"varint,2,opt,name=settings_revision,json=settingsRevision,proto3" json:"settings_revision,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetGatewayConfigResponse) Reset() { + *x = GetGatewayConfigResponse{} + mi := &file_sandbox_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetGatewayConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetGatewayConfigResponse) ProtoMessage() {} + +func (x *GetGatewayConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetGatewayConfigResponse.ProtoReflect.Descriptor instead. +func (*GetGatewayConfigResponse) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{17} +} + +func (x *GetGatewayConfigResponse) GetSettings() map[string]*SettingValue { + if x != nil { + return x.Settings + } + return nil +} + +func (x *GetGatewayConfigResponse) GetSettingsRevision() uint64 { + if x != nil { + return x.SettingsRevision + } + return 0 +} + +// Type-aware setting value for sandbox/gateway settings. +type SettingValue struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Value: + // + // *SettingValue_StringValue + // *SettingValue_BoolValue + // *SettingValue_IntValue + // *SettingValue_BytesValue + Value isSettingValue_Value `protobuf_oneof:"value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SettingValue) Reset() { + *x = SettingValue{} + mi := &file_sandbox_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SettingValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SettingValue) ProtoMessage() {} + +func (x *SettingValue) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SettingValue.ProtoReflect.Descriptor instead. +func (*SettingValue) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{18} +} + +func (x *SettingValue) GetValue() isSettingValue_Value { + if x != nil { + return x.Value + } + return nil +} + +func (x *SettingValue) GetStringValue() string { + if x != nil { + if x, ok := x.Value.(*SettingValue_StringValue); ok { + return x.StringValue + } + } + return "" +} + +func (x *SettingValue) GetBoolValue() bool { + if x != nil { + if x, ok := x.Value.(*SettingValue_BoolValue); ok { + return x.BoolValue + } + } + return false +} + +func (x *SettingValue) GetIntValue() int64 { + if x != nil { + if x, ok := x.Value.(*SettingValue_IntValue); ok { + return x.IntValue + } + } + return 0 +} + +func (x *SettingValue) GetBytesValue() []byte { + if x != nil { + if x, ok := x.Value.(*SettingValue_BytesValue); ok { + return x.BytesValue + } + } + return nil +} + +type isSettingValue_Value interface { + isSettingValue_Value() +} + +type SettingValue_StringValue struct { + StringValue string `protobuf:"bytes,1,opt,name=string_value,json=stringValue,proto3,oneof"` +} + +type SettingValue_BoolValue struct { + BoolValue bool `protobuf:"varint,2,opt,name=bool_value,json=boolValue,proto3,oneof"` +} + +type SettingValue_IntValue struct { + IntValue int64 `protobuf:"varint,3,opt,name=int_value,json=intValue,proto3,oneof"` +} + +type SettingValue_BytesValue struct { + BytesValue []byte `protobuf:"bytes,4,opt,name=bytes_value,json=bytesValue,proto3,oneof"` +} + +func (*SettingValue_StringValue) isSettingValue_Value() {} + +func (*SettingValue_BoolValue) isSettingValue_Value() {} + +func (*SettingValue_IntValue) isSettingValue_Value() {} + +func (*SettingValue_BytesValue) isSettingValue_Value() {} + +// Effective setting value and the scope it was resolved from. +type EffectiveSetting struct { + state protoimpl.MessageState `protogen:"open.v1"` + Value *SettingValue `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` + Scope SettingScope `protobuf:"varint,2,opt,name=scope,proto3,enum=openshell.sandbox.v1.SettingScope" json:"scope,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EffectiveSetting) Reset() { + *x = EffectiveSetting{} + mi := &file_sandbox_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EffectiveSetting) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EffectiveSetting) ProtoMessage() {} + +func (x *EffectiveSetting) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EffectiveSetting.ProtoReflect.Descriptor instead. +func (*EffectiveSetting) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{19} +} + +func (x *EffectiveSetting) GetValue() *SettingValue { + if x != nil { + return x.Value + } + return nil +} + +func (x *EffectiveSetting) GetScope() SettingScope { + if x != nil { + return x.Scope + } + return SettingScope_SETTING_SCOPE_UNSPECIFIED +} + +// Response containing effective sandbox settings and policy. +type GetSandboxConfigResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The sandbox policy configuration. + Policy *SandboxPolicy `protobuf:"bytes,1,opt,name=policy,proto3" json:"policy,omitempty"` + // Current policy version (monotonically increasing per sandbox). + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` + // SHA-256 hash of the serialized policy payload. + PolicyHash string `protobuf:"bytes,3,opt,name=policy_hash,json=policyHash,proto3" json:"policy_hash,omitempty"` + // Effective settings resolved for this sandbox, excluding the reserved policy key. + // Registered keys without a configured value are returned with an empty EffectiveSetting.value. + Settings map[string]*EffectiveSetting `protobuf:"bytes,4,rep,name=settings,proto3" json:"settings,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Fingerprint for effective config (policy + settings). Changes when any effective input changes. + ConfigRevision uint64 `protobuf:"varint,5,opt,name=config_revision,json=configRevision,proto3" json:"config_revision,omitempty"` + // Source of the policy payload for this response. + PolicySource PolicySource `protobuf:"varint,6,opt,name=policy_source,json=policySource,proto3,enum=openshell.sandbox.v1.PolicySource" json:"policy_source,omitempty"` + // When policy_source is GLOBAL, the version of the global policy revision. + // Zero when no global policy is active or when policy_source is SANDBOX. + GlobalPolicyVersion uint32 `protobuf:"varint,7,opt,name=global_policy_version,json=globalPolicyVersion,proto3" json:"global_policy_version,omitempty"` + // Fingerprint for provider credential inputs attached to this sandbox. + // Changes when attached provider names or attached provider records change. + ProviderEnvRevision uint64 `protobuf:"varint,8,opt,name=provider_env_revision,json=providerEnvRevision,proto3" json:"provider_env_revision,omitempty"` + // Operator-registered supervisor middleware services required by the + // effective policy. Built-in middleware is not included. + SupervisorMiddlewareServices []*SupervisorMiddlewareService `protobuf:"bytes,9,rep,name=supervisor_middleware_services,json=supervisorMiddlewareServices,proto3" json:"supervisor_middleware_services,omitempty"` + // Workspace the sandbox belongs to. Allows the supervisor to learn its + // workspace context for subsequent workspace-scoped RPCs. + Workspace string `protobuf:"bytes,10,opt,name=workspace,proto3" json:"workspace,omitempty"` + // Gateway-configured posture for rejected policy generations. Valid values + // are "fail_closed" and "retain_last_valid". Unknown or empty values must + // be treated as fail_closed by the supervisor. + PolicyValidationFailureMode string `protobuf:"bytes,11,opt,name=policy_validation_failure_mode,json=policyValidationFailureMode,proto3" json:"policy_validation_failure_mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSandboxConfigResponse) Reset() { + *x = GetSandboxConfigResponse{} + mi := &file_sandbox_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSandboxConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSandboxConfigResponse) ProtoMessage() {} + +func (x *GetSandboxConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSandboxConfigResponse.ProtoReflect.Descriptor instead. +func (*GetSandboxConfigResponse) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{20} +} + +func (x *GetSandboxConfigResponse) GetPolicy() *SandboxPolicy { + if x != nil { + return x.Policy + } + return nil +} + +func (x *GetSandboxConfigResponse) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *GetSandboxConfigResponse) GetPolicyHash() string { + if x != nil { + return x.PolicyHash + } + return "" +} + +func (x *GetSandboxConfigResponse) GetSettings() map[string]*EffectiveSetting { + if x != nil { + return x.Settings + } + return nil +} + +func (x *GetSandboxConfigResponse) GetConfigRevision() uint64 { + if x != nil { + return x.ConfigRevision + } + return 0 +} + +func (x *GetSandboxConfigResponse) GetPolicySource() PolicySource { + if x != nil { + return x.PolicySource + } + return PolicySource_POLICY_SOURCE_UNSPECIFIED +} + +func (x *GetSandboxConfigResponse) GetGlobalPolicyVersion() uint32 { + if x != nil { + return x.GlobalPolicyVersion + } + return 0 +} + +func (x *GetSandboxConfigResponse) GetProviderEnvRevision() uint64 { + if x != nil { + return x.ProviderEnvRevision + } + return 0 +} + +func (x *GetSandboxConfigResponse) GetSupervisorMiddlewareServices() []*SupervisorMiddlewareService { + if x != nil { + return x.SupervisorMiddlewareServices + } + return nil +} + +func (x *GetSandboxConfigResponse) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +func (x *GetSandboxConfigResponse) GetPolicyValidationFailureMode() string { + if x != nil { + return x.PolicyValidationFailureMode + } + return "" +} + +// Connection details for one operator-registered supervisor middleware service. +// V1 supports plaintext and server-authenticated TLS gRPC. +type SupervisorMiddlewareService struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Operator-owned registration name used by policy attachments and diagnostics. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // gRPC endpoint reachable from the sandbox supervisor. + GrpcEndpoint string `protobuf:"bytes,2,opt,name=grpc_endpoint,json=grpcEndpoint,proto3" json:"grpc_endpoint,omitempty"` + // Operator-owned body limit applied to every binding exposed by the service. + MaxBodyBytes uint64 `protobuf:"varint,3,opt,name=max_body_bytes,json=maxBodyBytes,proto3" json:"max_body_bytes,omitempty"` + // Default RPC timeout for this service. Empty uses the platform default of + // 500ms. Values use an integer with an `ms` or `s` suffix and must be + // between 10ms and 30s. + Timeout string `protobuf:"bytes,4,opt,name=timeout,proto3" json:"timeout,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SupervisorMiddlewareService) Reset() { + *x = SupervisorMiddlewareService{} + mi := &file_sandbox_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SupervisorMiddlewareService) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SupervisorMiddlewareService) ProtoMessage() {} + +func (x *SupervisorMiddlewareService) ProtoReflect() protoreflect.Message { + mi := &file_sandbox_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SupervisorMiddlewareService.ProtoReflect.Descriptor instead. +func (*SupervisorMiddlewareService) Descriptor() ([]byte, []int) { + return file_sandbox_proto_rawDescGZIP(), []int{21} +} + +func (x *SupervisorMiddlewareService) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SupervisorMiddlewareService) GetGrpcEndpoint() string { + if x != nil { + return x.GrpcEndpoint + } + return "" +} + +func (x *SupervisorMiddlewareService) GetMaxBodyBytes() uint64 { + if x != nil { + return x.MaxBodyBytes + } + return 0 +} + +func (x *SupervisorMiddlewareService) GetTimeout() string { + if x != nil { + return x.Timeout + } + return "" +} + +var File_sandbox_proto protoreflect.FileDescriptor + +const file_sandbox_proto_rawDesc = "" + + "\n" + + "\rsandbox.proto\x12\x14openshell.sandbox.v1\x1a\x1cgoogle/protobuf/struct.proto\"\xa8\x05\n" + + "\rSandboxPolicy\x12\x18\n" + + "\aversion\x18\x01 \x01(\rR\aversion\x12F\n" + + "\n" + + "filesystem\x18\x02 \x01(\v2&.openshell.sandbox.v1.FilesystemPolicyR\n" + + "filesystem\x12@\n" + + "\blandlock\x18\x03 \x01(\v2$.openshell.sandbox.v1.LandlockPolicyR\blandlock\x12=\n" + + "\aprocess\x18\x04 \x01(\v2#.openshell.sandbox.v1.ProcessPolicyR\aprocess\x12c\n" + + "\x10network_policies\x18\x05 \x03(\v28.openshell.sandbox.v1.SandboxPolicy.NetworkPoliciesEntryR\x0fnetworkPolicies\x12l\n" + + "\x13network_middlewares\x18\x06 \x03(\v2;.openshell.sandbox.v1.SandboxPolicy.NetworkMiddlewaresEntryR\x12networkMiddlewares\x1ak\n" + + "\x14NetworkPoliciesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12=\n" + + "\x05value\x18\x02 \x01(\v2'.openshell.sandbox.v1.NetworkPolicyRuleR\x05value:\x028\x01\x1at\n" + + "\x17NetworkMiddlewaresEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12C\n" + + "\x05value\x18\x02 \x01(\v2-.openshell.sandbox.v1.NetworkMiddlewareConfigR\x05value:\x028\x01\"w\n" + + "\x10FilesystemPolicy\x12'\n" + + "\x0finclude_workdir\x18\x01 \x01(\bR\x0eincludeWorkdir\x12\x1b\n" + + "\tread_only\x18\x02 \x03(\tR\breadOnly\x12\x1d\n" + + "\n" + + "read_write\x18\x03 \x03(\tR\treadWrite\"6\n" + + "\x0eLandlockPolicy\x12$\n" + + "\rcompatibility\x18\x01 \x01(\tR\rcompatibility\"Q\n" + + "\rProcessPolicy\x12\x1e\n" + + "\vrun_as_user\x18\x01 \x01(\tR\trunAsUser\x12 \n" + + "\frun_as_group\x18\x02 \x01(\tR\n" + + "runAsGroup\"\xad\x01\n" + + "\x11NetworkPolicyRule\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12C\n" + + "\tendpoints\x18\x02 \x03(\v2%.openshell.sandbox.v1.NetworkEndpointR\tendpoints\x12?\n" + + "\bbinaries\x18\x03 \x03(\v2#.openshell.sandbox.v1.NetworkBinaryR\bbinaries\"\xff\x01\n" + + "\x17NetworkMiddlewareConfig\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1e\n" + + "\n" + + "middleware\x18\x02 \x01(\tR\n" + + "middleware\x12/\n" + + "\x06config\x18\x03 \x01(\v2\x17.google.protobuf.StructR\x06config\x12\x19\n" + + "\bon_error\x18\x04 \x01(\tR\aonError\x12N\n" + + "\tendpoints\x18\x05 \x01(\v20.openshell.sandbox.v1.MiddlewareEndpointSelectorR\tendpoints\x12\x14\n" + + "\x05order\x18\x06 \x01(\x05R\x05order\"P\n" + + "\x1aMiddlewareEndpointSelector\x12\x18\n" + + "\ainclude\x18\x01 \x03(\tR\ainclude\x12\x18\n" + + "\aexclude\x18\x02 \x03(\tR\aexclude\"\x84\t\n" + + "\x0fNetworkEndpoint\x12\x12\n" + + "\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\x02 \x01(\rR\x04port\x12\x1a\n" + + "\bprotocol\x18\x03 \x01(\tR\bprotocol\x12\x10\n" + + "\x03tls\x18\x04 \x01(\tR\x03tls\x12 \n" + + "\venforcement\x18\x05 \x01(\tR\venforcement\x12\x16\n" + + "\x06access\x18\x06 \x01(\tR\x06access\x122\n" + + "\x05rules\x18\a \x03(\v2\x1c.openshell.sandbox.v1.L7RuleR\x05rules\x12\x1f\n" + + "\vallowed_ips\x18\b \x03(\tR\n" + + "allowedIps\x12\x14\n" + + "\x05ports\x18\t \x03(\rR\x05ports\x12?\n" + + "\n" + + "deny_rules\x18\n" + + " \x03(\v2 .openshell.sandbox.v1.L7DenyRuleR\tdenyRules\x12.\n" + + "\x13allow_encoded_slash\x18\v \x01(\bR\x11allowEncodedSlash\x12+\n" + + "\x11persisted_queries\x18\f \x01(\tR\x10persistedQueries\x12~\n" + + "\x19graphql_persisted_queries\x18\r \x03(\v2B.openshell.sandbox.v1.NetworkEndpoint.GraphqlPersistedQueriesEntryR\x17graphqlPersistedQueries\x123\n" + + "\x16graphql_max_body_bytes\x18\x0e \x01(\rR\x13graphqlMaxBodyBytes\x12\x12\n" + + "\x04path\x18\x0f \x01(\tR\x04path\x12@\n" + + "\x1cwebsocket_credential_rewrite\x18\x10 \x01(\bR\x1awebsocketCredentialRewrite\x12E\n" + + "\x1frequest_body_credential_rewrite\x18\x11 \x01(\bR\x1crequestBodyCredentialRewrite\x12)\n" + + "\x10advisor_proposed\x18\x12 \x01(\bR\x0fadvisorProposed\x12-\n" + + "\x12credential_signing\x18\x13 \x01(\tR\x11credentialSigning\x12'\n" + + "\x0fsigning_service\x18\x14 \x01(\tR\x0esigningService\x12%\n" + + "\x0esigning_region\x18\x15 \x01(\tR\rsigningRegion\x124\n" + + "\x17json_rpc_max_body_bytes\x18\x16 \x01(\rR\x13jsonRpcMaxBodyBytes\x122\n" + + "\x03mcp\x18\x17 \x01(\v2 .openshell.sandbox.v1.McpOptionsR\x03mcp\x1ar\n" + + "\x1cGraphqlPersistedQueriesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12<\n" + + "\x05value\x18\x02 \x01(\v2&.openshell.sandbox.v1.GraphqlOperationR\x05value:\x028\x01\"\xb6\x01\n" + + "\n" + + "McpOptions\x12/\n" + + "\x11strict_tool_names\x18\x01 \x01(\bH\x00R\x0fstrictToolNames\x88\x01\x01\x12A\n" + + "\x1ballow_all_known_mcp_methods\x18\x02 \x01(\bH\x01R\x17allowAllKnownMcpMethods\x88\x01\x01B\x14\n" + + "\x12_strict_tool_namesB\x1e\n" + + "\x1c_allow_all_known_mcp_methods\"x\n" + + "\x10GraphqlOperation\x12%\n" + + "\x0eoperation_type\x18\x01 \x01(\tR\roperationType\x12%\n" + + "\x0eoperation_name\x18\x02 \x01(\tR\roperationName\x12\x16\n" + + "\x06fields\x18\x03 \x03(\tR\x06fields\"\x88\x04\n" + + "\n" + + "L7DenyRule\x12\x16\n" + + "\x06method\x18\x01 \x01(\tR\x06method\x12\x12\n" + + "\x04path\x18\x02 \x01(\tR\x04path\x12\x18\n" + + "\acommand\x18\x03 \x01(\tR\acommand\x12A\n" + + "\x05query\x18\x04 \x03(\v2+.openshell.sandbox.v1.L7DenyRule.QueryEntryR\x05query\x12%\n" + + "\x0eoperation_type\x18\x05 \x01(\tR\roperationType\x12%\n" + + "\x0eoperation_name\x18\x06 \x01(\tR\roperationName\x12\x16\n" + + "\x06fields\x18\a \x03(\tR\x06fields\x12D\n" + + "\x06params\x18\t \x03(\v2,.openshell.sandbox.v1.L7DenyRule.ParamsEntryR\x06params\x1a^\n" + + "\n" + + "QueryEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12:\n" + + "\x05value\x18\x02 \x01(\v2$.openshell.sandbox.v1.L7QueryMatcherR\x05value:\x028\x01\x1a_\n" + + "\vParamsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12:\n" + + "\x05value\x18\x02 \x01(\v2$.openshell.sandbox.v1.L7QueryMatcherR\x05value:\x028\x01J\x04\b\b\x10\t\"=\n" + + "\x06L7Rule\x123\n" + + "\x05allow\x18\x01 \x01(\v2\x1d.openshell.sandbox.v1.L7AllowR\x05allow\"\xff\x03\n" + + "\aL7Allow\x12\x16\n" + + "\x06method\x18\x01 \x01(\tR\x06method\x12\x12\n" + + "\x04path\x18\x02 \x01(\tR\x04path\x12\x18\n" + + "\acommand\x18\x03 \x01(\tR\acommand\x12>\n" + + "\x05query\x18\x04 \x03(\v2(.openshell.sandbox.v1.L7Allow.QueryEntryR\x05query\x12%\n" + + "\x0eoperation_type\x18\x05 \x01(\tR\roperationType\x12%\n" + + "\x0eoperation_name\x18\x06 \x01(\tR\roperationName\x12\x16\n" + + "\x06fields\x18\a \x03(\tR\x06fields\x12A\n" + + "\x06params\x18\t \x03(\v2).openshell.sandbox.v1.L7Allow.ParamsEntryR\x06params\x1a^\n" + + "\n" + + "QueryEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12:\n" + + "\x05value\x18\x02 \x01(\v2$.openshell.sandbox.v1.L7QueryMatcherR\x05value:\x028\x01\x1a_\n" + + "\vParamsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12:\n" + + "\x05value\x18\x02 \x01(\v2$.openshell.sandbox.v1.L7QueryMatcherR\x05value:\x028\x01J\x04\b\b\x10\t\"6\n" + + "\x0eL7QueryMatcher\x12\x12\n" + + "\x04glob\x18\x01 \x01(\tR\x04glob\x12\x10\n" + + "\x03any\x18\x02 \x03(\tR\x03any\"A\n" + + "\rNetworkBinary\x12\x12\n" + + "\x04path\x18\x01 \x01(\tR\x04path\x12\x1c\n" + + "\aharness\x18\x02 \x01(\bB\x02\x18\x01R\aharness\"8\n" + + "\x17GetSandboxConfigRequest\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\"\x19\n" + + "\x17GetGatewayConfigRequest\"\x82\x02\n" + + "\x18GetGatewayConfigResponse\x12X\n" + + "\bsettings\x18\x01 \x03(\v2<.openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntryR\bsettings\x12+\n" + + "\x11settings_revision\x18\x02 \x01(\x04R\x10settingsRevision\x1a_\n" + + "\rSettingsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x128\n" + + "\x05value\x18\x02 \x01(\v2\".openshell.sandbox.v1.SettingValueR\x05value:\x028\x01\"\x9f\x01\n" + + "\fSettingValue\x12#\n" + + "\fstring_value\x18\x01 \x01(\tH\x00R\vstringValue\x12\x1f\n" + + "\n" + + "bool_value\x18\x02 \x01(\bH\x00R\tboolValue\x12\x1d\n" + + "\tint_value\x18\x03 \x01(\x03H\x00R\bintValue\x12!\n" + + "\vbytes_value\x18\x04 \x01(\fH\x00R\n" + + "bytesValueB\a\n" + + "\x05value\"\x86\x01\n" + + "\x10EffectiveSetting\x128\n" + + "\x05value\x18\x01 \x01(\v2\".openshell.sandbox.v1.SettingValueR\x05value\x128\n" + + "\x05scope\x18\x02 \x01(\x0e2\".openshell.sandbox.v1.SettingScopeR\x05scope\"\x87\x06\n" + + "\x18GetSandboxConfigResponse\x12;\n" + + "\x06policy\x18\x01 \x01(\v2#.openshell.sandbox.v1.SandboxPolicyR\x06policy\x12\x18\n" + + "\aversion\x18\x02 \x01(\rR\aversion\x12\x1f\n" + + "\vpolicy_hash\x18\x03 \x01(\tR\n" + + "policyHash\x12X\n" + + "\bsettings\x18\x04 \x03(\v2<.openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntryR\bsettings\x12'\n" + + "\x0fconfig_revision\x18\x05 \x01(\x04R\x0econfigRevision\x12G\n" + + "\rpolicy_source\x18\x06 \x01(\x0e2\".openshell.sandbox.v1.PolicySourceR\fpolicySource\x122\n" + + "\x15global_policy_version\x18\a \x01(\rR\x13globalPolicyVersion\x122\n" + + "\x15provider_env_revision\x18\b \x01(\x04R\x13providerEnvRevision\x12w\n" + + "\x1esupervisor_middleware_services\x18\t \x03(\v21.openshell.sandbox.v1.SupervisorMiddlewareServiceR\x1csupervisorMiddlewareServices\x12\x1c\n" + + "\tworkspace\x18\n" + + " \x01(\tR\tworkspace\x12C\n" + + "\x1epolicy_validation_failure_mode\x18\v \x01(\tR\x1bpolicyValidationFailureMode\x1ac\n" + + "\rSettingsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12<\n" + + "\x05value\x18\x02 \x01(\v2&.openshell.sandbox.v1.EffectiveSettingR\x05value:\x028\x01\"\x96\x01\n" + + "\x1bSupervisorMiddlewareService\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12#\n" + + "\rgrpc_endpoint\x18\x02 \x01(\tR\fgrpcEndpoint\x12$\n" + + "\x0emax_body_bytes\x18\x03 \x01(\x04R\fmaxBodyBytes\x12\x18\n" + + "\atimeout\x18\x04 \x01(\tR\atimeout*b\n" + + "\fSettingScope\x12\x1d\n" + + "\x19SETTING_SCOPE_UNSPECIFIED\x10\x00\x12\x19\n" + + "\x15SETTING_SCOPE_SANDBOX\x10\x01\x12\x18\n" + + "\x14SETTING_SCOPE_GLOBAL\x10\x02*b\n" + + "\fPolicySource\x12\x1d\n" + + "\x19POLICY_SOURCE_UNSPECIFIED\x10\x00\x12\x19\n" + + "\x15POLICY_SOURCE_SANDBOX\x10\x01\x12\x18\n" + + "\x14POLICY_SOURCE_GLOBAL\x10\x02b\x06proto3" + +var ( + file_sandbox_proto_rawDescOnce sync.Once + file_sandbox_proto_rawDescData []byte +) + +func file_sandbox_proto_rawDescGZIP() []byte { + file_sandbox_proto_rawDescOnce.Do(func() { + file_sandbox_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_sandbox_proto_rawDesc), len(file_sandbox_proto_rawDesc))) + }) + return file_sandbox_proto_rawDescData +} + +var file_sandbox_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_sandbox_proto_msgTypes = make([]protoimpl.MessageInfo, 31) +var file_sandbox_proto_goTypes = []any{ + (SettingScope)(0), // 0: openshell.sandbox.v1.SettingScope + (PolicySource)(0), // 1: openshell.sandbox.v1.PolicySource + (*SandboxPolicy)(nil), // 2: openshell.sandbox.v1.SandboxPolicy + (*FilesystemPolicy)(nil), // 3: openshell.sandbox.v1.FilesystemPolicy + (*LandlockPolicy)(nil), // 4: openshell.sandbox.v1.LandlockPolicy + (*ProcessPolicy)(nil), // 5: openshell.sandbox.v1.ProcessPolicy + (*NetworkPolicyRule)(nil), // 6: openshell.sandbox.v1.NetworkPolicyRule + (*NetworkMiddlewareConfig)(nil), // 7: openshell.sandbox.v1.NetworkMiddlewareConfig + (*MiddlewareEndpointSelector)(nil), // 8: openshell.sandbox.v1.MiddlewareEndpointSelector + (*NetworkEndpoint)(nil), // 9: openshell.sandbox.v1.NetworkEndpoint + (*McpOptions)(nil), // 10: openshell.sandbox.v1.McpOptions + (*GraphqlOperation)(nil), // 11: openshell.sandbox.v1.GraphqlOperation + (*L7DenyRule)(nil), // 12: openshell.sandbox.v1.L7DenyRule + (*L7Rule)(nil), // 13: openshell.sandbox.v1.L7Rule + (*L7Allow)(nil), // 14: openshell.sandbox.v1.L7Allow + (*L7QueryMatcher)(nil), // 15: openshell.sandbox.v1.L7QueryMatcher + (*NetworkBinary)(nil), // 16: openshell.sandbox.v1.NetworkBinary + (*GetSandboxConfigRequest)(nil), // 17: openshell.sandbox.v1.GetSandboxConfigRequest + (*GetGatewayConfigRequest)(nil), // 18: openshell.sandbox.v1.GetGatewayConfigRequest + (*GetGatewayConfigResponse)(nil), // 19: openshell.sandbox.v1.GetGatewayConfigResponse + (*SettingValue)(nil), // 20: openshell.sandbox.v1.SettingValue + (*EffectiveSetting)(nil), // 21: openshell.sandbox.v1.EffectiveSetting + (*GetSandboxConfigResponse)(nil), // 22: openshell.sandbox.v1.GetSandboxConfigResponse + (*SupervisorMiddlewareService)(nil), // 23: openshell.sandbox.v1.SupervisorMiddlewareService + nil, // 24: openshell.sandbox.v1.SandboxPolicy.NetworkPoliciesEntry + nil, // 25: openshell.sandbox.v1.SandboxPolicy.NetworkMiddlewaresEntry + nil, // 26: openshell.sandbox.v1.NetworkEndpoint.GraphqlPersistedQueriesEntry + nil, // 27: openshell.sandbox.v1.L7DenyRule.QueryEntry + nil, // 28: openshell.sandbox.v1.L7DenyRule.ParamsEntry + nil, // 29: openshell.sandbox.v1.L7Allow.QueryEntry + nil, // 30: openshell.sandbox.v1.L7Allow.ParamsEntry + nil, // 31: openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry + nil, // 32: openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry + (*structpb.Struct)(nil), // 33: google.protobuf.Struct +} +var file_sandbox_proto_depIdxs = []int32{ + 3, // 0: openshell.sandbox.v1.SandboxPolicy.filesystem:type_name -> openshell.sandbox.v1.FilesystemPolicy + 4, // 1: openshell.sandbox.v1.SandboxPolicy.landlock:type_name -> openshell.sandbox.v1.LandlockPolicy + 5, // 2: openshell.sandbox.v1.SandboxPolicy.process:type_name -> openshell.sandbox.v1.ProcessPolicy + 24, // 3: openshell.sandbox.v1.SandboxPolicy.network_policies:type_name -> openshell.sandbox.v1.SandboxPolicy.NetworkPoliciesEntry + 25, // 4: openshell.sandbox.v1.SandboxPolicy.network_middlewares:type_name -> openshell.sandbox.v1.SandboxPolicy.NetworkMiddlewaresEntry + 9, // 5: openshell.sandbox.v1.NetworkPolicyRule.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 16, // 6: openshell.sandbox.v1.NetworkPolicyRule.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 33, // 7: openshell.sandbox.v1.NetworkMiddlewareConfig.config:type_name -> google.protobuf.Struct + 8, // 8: openshell.sandbox.v1.NetworkMiddlewareConfig.endpoints:type_name -> openshell.sandbox.v1.MiddlewareEndpointSelector + 13, // 9: openshell.sandbox.v1.NetworkEndpoint.rules:type_name -> openshell.sandbox.v1.L7Rule + 12, // 10: openshell.sandbox.v1.NetworkEndpoint.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 26, // 11: openshell.sandbox.v1.NetworkEndpoint.graphql_persisted_queries:type_name -> openshell.sandbox.v1.NetworkEndpoint.GraphqlPersistedQueriesEntry + 10, // 12: openshell.sandbox.v1.NetworkEndpoint.mcp:type_name -> openshell.sandbox.v1.McpOptions + 27, // 13: openshell.sandbox.v1.L7DenyRule.query:type_name -> openshell.sandbox.v1.L7DenyRule.QueryEntry + 28, // 14: openshell.sandbox.v1.L7DenyRule.params:type_name -> openshell.sandbox.v1.L7DenyRule.ParamsEntry + 14, // 15: openshell.sandbox.v1.L7Rule.allow:type_name -> openshell.sandbox.v1.L7Allow + 29, // 16: openshell.sandbox.v1.L7Allow.query:type_name -> openshell.sandbox.v1.L7Allow.QueryEntry + 30, // 17: openshell.sandbox.v1.L7Allow.params:type_name -> openshell.sandbox.v1.L7Allow.ParamsEntry + 31, // 18: openshell.sandbox.v1.GetGatewayConfigResponse.settings:type_name -> openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry + 20, // 19: openshell.sandbox.v1.EffectiveSetting.value:type_name -> openshell.sandbox.v1.SettingValue + 0, // 20: openshell.sandbox.v1.EffectiveSetting.scope:type_name -> openshell.sandbox.v1.SettingScope + 2, // 21: openshell.sandbox.v1.GetSandboxConfigResponse.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 32, // 22: openshell.sandbox.v1.GetSandboxConfigResponse.settings:type_name -> openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry + 1, // 23: openshell.sandbox.v1.GetSandboxConfigResponse.policy_source:type_name -> openshell.sandbox.v1.PolicySource + 23, // 24: openshell.sandbox.v1.GetSandboxConfigResponse.supervisor_middleware_services:type_name -> openshell.sandbox.v1.SupervisorMiddlewareService + 6, // 25: openshell.sandbox.v1.SandboxPolicy.NetworkPoliciesEntry.value:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 7, // 26: openshell.sandbox.v1.SandboxPolicy.NetworkMiddlewaresEntry.value:type_name -> openshell.sandbox.v1.NetworkMiddlewareConfig + 11, // 27: openshell.sandbox.v1.NetworkEndpoint.GraphqlPersistedQueriesEntry.value:type_name -> openshell.sandbox.v1.GraphqlOperation + 15, // 28: openshell.sandbox.v1.L7DenyRule.QueryEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher + 15, // 29: openshell.sandbox.v1.L7DenyRule.ParamsEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher + 15, // 30: openshell.sandbox.v1.L7Allow.QueryEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher + 15, // 31: openshell.sandbox.v1.L7Allow.ParamsEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher + 20, // 32: openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry.value:type_name -> openshell.sandbox.v1.SettingValue + 21, // 33: openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry.value:type_name -> openshell.sandbox.v1.EffectiveSetting + 34, // [34:34] is the sub-list for method output_type + 34, // [34:34] is the sub-list for method input_type + 34, // [34:34] is the sub-list for extension type_name + 34, // [34:34] is the sub-list for extension extendee + 0, // [0:34] is the sub-list for field type_name +} + +func init() { file_sandbox_proto_init() } +func file_sandbox_proto_init() { + if File_sandbox_proto != nil { + return + } + file_sandbox_proto_msgTypes[8].OneofWrappers = []any{} + file_sandbox_proto_msgTypes[18].OneofWrappers = []any{ + (*SettingValue_StringValue)(nil), + (*SettingValue_BoolValue)(nil), + (*SettingValue_IntValue)(nil), + (*SettingValue_BytesValue)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_sandbox_proto_rawDesc), len(file_sandbox_proto_rawDesc)), + NumEnums: 2, + NumMessages: 31, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_sandbox_proto_goTypes, + DependencyIndexes: file_sandbox_proto_depIdxs, + EnumInfos: file_sandbox_proto_enumTypes, + MessageInfos: file_sandbox_proto_msgTypes, + }.Build() + File_sandbox_proto = out.File + file_sandbox_proto_goTypes = nil + file_sandbox_proto_depIdxs = nil +} diff --git a/tasks/ci.toml b/tasks/ci.toml index a3ed236ae..7294da9d0 100644 --- a/tasks/ci.toml +++ b/tasks/ci.toml @@ -56,7 +56,7 @@ hide = true [ci] description = "Run full checks (lint, compile/type checks, and tests)" -depends = ["lint", "check", "test"] +depends = ["lint", "check", "test", "go:ci"] [all] description = "Alias for ci" diff --git a/tasks/go.toml b/tasks/go.toml new file mode 100644 index 000000000..2a80b9a49 --- /dev/null +++ b/tasks/go.toml @@ -0,0 +1,163 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Go SDK development, build, lint, and format tasks + +["go:test"] +description = "Run Go SDK unit tests with coverage" +dir = "sdk/go" +run = "go test -coverprofile=coverage.out -coverpkg=./openshell/... -race ./..." +hide = true + +["go:test:integration"] +description = "Run Go SDK integration tests" +dir = "sdk/go" +run = "go test -tags=integration -race ./..." +hide = true + +["go:lint"] +description = "Run Go SDK linter" +dir = "sdk/go" +run = "golangci-lint run ./..." +hide = true + +["go:fmt"] +description = "Format Go SDK code" +dir = "sdk/go" +run = "goimports -w . && go fmt ./..." +hide = true + +["go:build"] +description = "Build Go SDK packages" +dir = "sdk/go" +run = "go build ./..." +hide = true + +["go:format:check"] +description = "Verify Go SDK code is gofmt-formatted" +dir = "sdk/go" +run = """ +#!/usr/bin/env bash +set -euo pipefail +UNFORMATTED=$(gofmt -l . 2>/dev/null || true) +if [ -n "$UNFORMATTED" ]; then + echo "ERROR: The following files are not gofmt-formatted:" + echo "$UNFORMATTED" + exit 1 +fi +""" +hide = true + +["go:ci"] +description = "Run Go SDK full CI pipeline" +depends = ["go:format:check", "go:lint", "go:build", "go:test", "go:proto:check", "go:docs:check"] + +["go:docs:check"] +description = "Verify every public Go SDK package has a docs page" +dir = "sdk/go" +run = """ +#!/usr/bin/env bash +set -euo pipefail + +DOCS_DIR="docs/src/api" +SUMMARY="docs/src/SUMMARY.md" +MISSING=0 + +# Find all public packages with a doc.go (excluding internal, proto, types) +for docfile in openshell/v1/*/doc.go; do + pkg=$(basename "$(dirname "$docfile")") + + # Skip internal packages and types (no user-facing docs needed) + case "$pkg" in + internal|types) continue ;; + esac + + # Check for matching docs page + if [ ! -f "$DOCS_DIR/$pkg.md" ]; then + echo "MISSING: $DOCS_DIR/$pkg.md (package openshell/v1/$pkg has doc.go but no docs page)" + MISSING=$((MISSING + 1)) + fi + + # Check for SUMMARY.md entry + if ! grep -q "api/$pkg.md" "$SUMMARY" 2>/dev/null; then + echo "MISSING: SUMMARY.md entry for api/$pkg.md" + MISSING=$((MISSING + 1)) + fi +done + +if [ "$MISSING" -gt 0 ]; then + echo "" + echo "ERROR: $MISSING documentation gaps found." + echo "Every public package with doc.go needs a docs/src/api/.md page" + echo "and a SUMMARY.md entry. See Constitution XIII." + exit 1 +fi + +echo "Docs check passed: all public packages have documentation." +""" +hide = true + +["go:proto:gen"] +description = "Generate Go bindings from proto files using buf" +dir = "sdk/go" +run = """ +#!/usr/bin/env bash +set -euo pipefail + +for tool in buf protoc-gen-go protoc-gen-go-grpc; do + if ! command -v "$tool" &>/dev/null; then + echo "ERROR: $tool not found. Run 'mise install' to install it." + exit 1 + fi +done + +# Clean previous output before regeneration +find proto -name '*.pb.go' -delete 2>/dev/null || true + +buf generate + +echo "Proto generation complete." +echo "Generated packages:" +for pkg in openshellv1 datamodelv1 sandboxv1 optionsv1; do + count=$(find "proto/$pkg" -name '*.go' 2>/dev/null | wc -l | tr -d ' ') + echo " proto/$pkg/: $count files" +done +""" +hide = true + +["go:proto:check"] +description = "Verify generated Go SDK proto files are up to date" +dir = "sdk/go" +run = """ +#!/usr/bin/env bash +set -euo pipefail + +for tool in buf protoc-gen-go protoc-gen-go-grpc; do + if ! command -v "$tool" &>/dev/null; then + echo "ERROR: $tool not found. Run 'mise install' to install it." + exit 1 + fi +done + +WORK_DIR=$(mktemp -d) +trap 'rm -rf "$WORK_DIR"' EXIT + +# Generate to temp directory with adjusted output path +sed "s|out: \\.|out: $WORK_DIR|" buf.gen.yaml > "$WORK_DIR/buf.gen.yaml" +buf generate --template "$WORK_DIR/buf.gen.yaml" + +DIFF_OUTPUT=$(diff -r "$WORK_DIR/proto" "proto" \ + --exclude="*.proto" \ + 2>&1) || true + +if [ -n "$DIFF_OUTPUT" ]; then + echo "ERROR: Generated proto files are out of date." + echo "Run 'mise run go:proto:gen' to regenerate." + echo "" + echo "$DIFF_OUTPUT" + exit 1 +fi + +echo "Proto check passed: generated files are up to date." +""" +hide = true From 85d992f768bf2152fc5b815fc4031c61eae1ac52 Mon Sep 17 00:00:00 2001 From: "John T. Myers" <9696606+johntmyers@users.noreply.github.com> Date: Wed, 5 Aug 2026 14:55:18 -0700 Subject: [PATCH 3/8] RFC 0005: Sandbox proxy egress adapter model (#2155) * docs(rfc): propose sandbox proxy egress adapter model Signed-off-by: John Myers * docs(rfc): propose sandbox proxy egress adapter model Signed-off-by: John Myers * docs(rfc): update sandbox proxy adapter proposal Signed-off-by: John Myers * docs(rfc): account for supervisor middleware Signed-off-by: John Myers * docs(rfc): include json-rpc and mcp l7 protocols Signed-off-by: John Myers * docs(rfc): make process identity optional Signed-off-by: John Myers * docs(rfc): clarify relay flow diagram Signed-off-by: John Myers * docs(rfc): address proxy adapter review feedback Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * docs(rfc): enforce policy after middleware mutation Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * docs(rfc): define synthetic DNS correlation Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> --------- Signed-off-by: John Myers Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> Co-authored-by: John Myers --- .../README.md | 589 +++++++++++++++++ .../current-shape.md | 286 +++++++++ .../implementation-plan.md | 279 ++++++++ .../technical-design.md | 594 ++++++++++++++++++ 4 files changed, 1748 insertions(+) create mode 100644 rfc/0005-sandbox-proxy-egress-adapter/README.md create mode 100644 rfc/0005-sandbox-proxy-egress-adapter/current-shape.md create mode 100644 rfc/0005-sandbox-proxy-egress-adapter/implementation-plan.md create mode 100644 rfc/0005-sandbox-proxy-egress-adapter/technical-design.md diff --git a/rfc/0005-sandbox-proxy-egress-adapter/README.md b/rfc/0005-sandbox-proxy-egress-adapter/README.md new file mode 100644 index 000000000..c0a43849e --- /dev/null +++ b/rfc/0005-sandbox-proxy-egress-adapter/README.md @@ -0,0 +1,589 @@ +--- +authors: + - "@johntmyers" +state: review +links: + - https://github.com/NVIDIA/OpenShell/issues/1107 + - https://github.com/NVIDIA/OpenShell/pull/2155 + - https://github.com/NVIDIA/OpenShell/pull/1083 + - https://github.com/NVIDIA/OpenShell/pull/1151 + - https://github.com/NVIDIA/OpenShell/pull/1286 + - https://github.com/NVIDIA/OpenShell/pull/1511 + - https://github.com/NVIDIA/OpenShell/pull/1738 + - https://github.com/NVIDIA/OpenShell/pull/2027 + - https://github.com/NVIDIA/OpenShell/pull/1865 + - https://github.com/NVIDIA/OpenShell/pull/1938 +--- + +# RFC 0005 - Sandbox Proxy Egress Adapter Model + + + +## Summary + +Refactor sandbox egress around shared authorization, destination-validation, +and relay boundaries. CONNECT, forward HTTP, native TCP capture, policy DNS, +`inference.local`, `policy.local`, and metadata loopback become narrow adapters +that translate userland entry points into common runtime intents. Policy +evaluation, destination validation, supervisor middleware, credential +injection, request-body rewrite, WebSocket handling, protocol processing, and +upstream dialing happen behind shared boundaries. + +The RFC describes the complete forward-looking architecture. It is designed +to land incrementally across multiple pull requests. The first milestone only +restructures CONNECT and forward HTTP, extracts shared primitives, and +preserves every current user-facing feature and enforcement behavior. Later +milestones add policy DNS, transparent TCP capture, native protocol processors, +and optional deployment shapes on top of those boundaries. + +The codebase has already moved in this direction by splitting networking into +`openshell-supervisor-network` and process/netns work into +`openshell-supervisor-process`. This RFC proposes the next internal boundary: +make proxy entry mechanisms pluggable without duplicating authorization, +destination validation, or relay behavior. + +Supporting detail lives in: + +- [Current shape appendix](current-shape.md) +- [Technical design appendix](technical-design.md) +- [Implementation plan](implementation-plan.md) + +## Motivation + +The sandbox proxy supports several connection surfaces: explicit CONNECT, +forward HTTP, local inference and policy APIs, metadata loopback, TLS +termination, REST, GraphQL, JSON-RPC, MCP, and WebSocket inspection, +credential injection, supervisor middleware, and nftables-backed bypass +detection. These features are valuable, but changes to policy and enforcement +still tend to touch multiple entry paths. + +The risk is asymmetric enforcement. A security fix can be added to CONNECT and +missed in forward HTTP; endpoint metadata can be selected differently from the +logged policy; a credential path can gain request-body or WebSocket support +without the same behavior existing in another relay mode. + +The target shape separates three concerns: + +- **Adapters** describe how userland reached the networking component. +- **Authorization** decides whether the egress is allowed and what endpoint + behavior applies. +- **Relays** own bytes, credentials, protocol parsing, and upstream dialing. + +The first milestone targets the current embedded/network-only supervisor +runtime and preserves its existing user-facing behavior while the internal +seams move. The same boundaries then support policy DNS and transparent TCP, +native protocol processing, and future deployment modes without duplicating +authorization or relay logic. + +## Non-goals + +- Replace CONNECT with forward proxy as the only explicit proxy mode. +- Add SOCKS support. +- Add HTTP/2 L7 parsing in this refactor. Inspected HTTP paths should continue + to reject unsupported h2c upgrades instead of silently upgrading to raw + traffic. +- Redesign provider credential storage. +- Reintroduce iptables as the sandbox packet filtering backend. +- Use eBPF connect hooks for transparent capture. Native TCP capture needs a + userland proxy in the byte stream for TLS termination and protocol parsing. +- Add policy-declared supervisor-proxied host-local endpoints. Issue + [#1633](https://github.com/NVIDIA/OpenShell/issues/1633) can consume these + boundaries in separate feature work. +- Change the existing endpoint-only runtime's process-identity semantics during + the compatibility refactor. Future identity-less deployment modes require an + explicit capability contract and cannot inherit endpoint-only behavior by + accident. + +## Proposal + +### Migration Big Rocks + +1. **Transport and local-service adapters.** CONNECT, forward HTTP, + transparent TCP, policy DNS, `inference.local`, `policy.local`, and metadata + loopback become small adapters. They parse their surface and produce either + an egress intent, a local response, or a DNS answer. They do not duplicate + policy evaluation. +2. **Egress intent and decision.** Shared authorization evaluates L4 policy and + endpoint selection once per connection intent and returns one decision + containing the matched policy, matched endpoint, optional process identity + evidence used for evaluation, allowed IP metadata, TLS behavior, protocol + enforcement, and credential injection and middleware plans. +3. **Relays.** Relays receive an authorized destination connector, not an + already-open upstream socket. HTTP relays evaluate every request before + upstream write. TCP relays copy bytes for L4-only endpoints or hand the + stream to a protocol processor when endpoint policy requires native protocol + enforcement. + +The first implementation milestone populates a compatibility +`EgressDecision` through the existing separate queries so type extraction does +not change behavior. That transitional envelope is not the target single +authorization result. Generation-consistent materialization and deterministic +endpoint selection cut over separately after shadow comparison, before later +transport adapters depend on the result. + +### Unified Adapter Flow + +```mermaid +flowchart TD + User["Userland payload / harness"] + + subgraph ExplicitProxy["Explicit proxy listener"] + ProxyBytes["HTTP proxy bytes"] + IsConnect{"CONNECT request?"} + Connect["CONNECT adapter"] + Forward["Forward HTTP adapter"] + ProxyBytes --> IsConnect + IsConnect -- Yes --> Connect + IsConnect -- No --> Forward + end + + subgraph NativeTcp["Policy DNS + native TCP"] + NameLookup["Userland DNS lookup"] + PolicyDns["Policy DNS adapter"] + DnsEligible{"Eligible native TCP
policy endpoint?"} + DnsDeny["Local DNS refusal
no upstream lookup"] + TrustedDns["Trusted upstream lookup
and destination filtering"] + DnsMapping["Synthetic IP + active mapping
to validated real addresses"] + DnsAnswer["Return synthetic IP"] + NativeConnect["Userland connect(synthetic_ip:port)"] + TcpAdapter["Transparent TCP adapter
recover mapping"] + NameLookup --> PolicyDns + PolicyDns --> DnsEligible + DnsEligible -- No --> DnsDeny + DnsEligible -- Yes --> TrustedDns + TrustedDns --> DnsMapping + DnsMapping --> DnsAnswer + DnsAnswer --> NativeConnect + NativeConnect --> TcpAdapter + end + + subgraph LocalApis["Sandbox-local services"] + InferenceReq["Request to inference.local"] + PolicyReq["Request to policy.local"] + MetadataReq["Request to metadata loopback"] + InferenceAdapter["Inference local adapter"] + PolicyAdapter["Policy local adapter"] + MetadataAdapter["Metadata loopback adapter"] + InferenceReq --> InferenceAdapter + PolicyReq --> PolicyAdapter + MetadataReq --> MetadataAdapter + end + + subgraph Shared["Shared external egress pipeline"] + Intent["EgressIntent"] + Auth["Authorize and select endpoint"] + Decision["EgressDecision"] + Validate["Resolve or consume pinned destination
and validate"] + Relay["Relay"] + Deny["Adapter-specific deny response"] + Intent --> Auth + Auth --> Allowed{"Allowed?"} + Allowed -- No --> Deny + Allowed -- Yes --> Decision + Decision --> Validate + Validate --> Relay + end + + User --> ProxyBytes + User --> NameLookup + User --> NativeConnect + User --> InferenceReq + User --> PolicyReq + User --> MetadataReq + + Connect --> Intent + Forward --> Intent + TcpAdapter --> Intent + InferenceAdapter --> InferenceResp["Local inference response"] + PolicyAdapter --> PolicyResp["Local policy response"] + MetadataAdapter --> MetadataResp["Local metadata credential response"] +``` + +Each adapter still owns its response shape. If authorization denies a CONNECT +intent, the CONNECT adapter returns a tunnel denial. If forward HTTP is denied, +the forward adapter returns an HTTP denial. If policy DNS refuses a name, it +returns the appropriate DNS response. The shared layer decides the outcome; +the adapter renders it for its protocol. + +### Relay Flow + +```mermaid +flowchart TD + Start["Authorized egress + destination connector"] + Start --> FirstReq{"Forward HTTP adapter
already has first request?"} + + FirstReq -- Yes --> ForwardEnforcement{"Endpoint enforcement"} + ForwardEnforcement -- "None or HTTP" --> HttpReq["Parsed HTTP request"] + ForwardEnforcement -- "Protocol processor" --> BadForward["Deny: HTTP request for native protocol endpoint"] + + FirstReq -- No --> Prepare["Prepare readable client stream"] + Prepare --> TlsPolicy{"TLS handling enabled?"} + TlsPolicy -- No --> Readable["Client stream"] + TlsPolicy -- Yes --> Peek["Peek client bytes"] + Peek --> Tls{"TLS ClientHello?"} + Tls -- Yes --> Terminate["Shared TLS terminator"] + Tls -- No --> Readable + Terminate --> Readable + + Readable --> Enforce{"Endpoint enforcement"} + Enforce -- "None" --> Sniff{"HTTP request detected?"} + Sniff -- Yes --> ParseHttp["Parse HTTP request"] + Sniff -- No --> TcpRelay["TcpRelay
connect upstream and copy bytes"] + ParseHttp --> HttpReq + + Enforce -- "HTTP" --> MustHttp{"HTTP request detected?"} + MustHttp -- Yes --> ParseHttp + MustHttp -- No --> DenyHttp["Deny: expected HTTP"] + + Enforce -- "Protocol processor" --> Processor["TcpRelay hands stream to protocol processor"] + Processor --> ProcessorOwns["Processor owns message loop
and calls connector when allowed"] + + subgraph HttpLoop["HTTP relay request loop"] + HttpReq --> HttpMode{"HTTP endpoint policy?"} + HttpMode -- "L4-only HTTP" --> ReqAllowed["Request admitted by connection decision"] + HttpMode -- "REST / GraphQL / JSON-RPC / MCP / WebSocket" --> ReqPolicy{"Request policy allowed?"} + ReqPolicy -- No --> ReqDeny["Local HTTP deny
no upstream write"] + ReqPolicy -- Yes --> ReqAllowed + ReqAllowed --> Middleware{"Supervisor middleware
configured?"} + Middleware -- Yes --> MwEval["Run HTTP_REQUEST / PRE_CREDENTIALS middleware"] + Middleware -- No --> Creds["Resolve static placeholders
and token grants"] + MwEval --> MwAllowed{"Middleware allowed?"} + MwAllowed -- No --> MwDeny["Local middleware deny
no credential injection"] + MwAllowed -- Yes --> Recheck["Re-parse transformed protocol body
and re-evaluate request policy"] + Recheck --> PostMwAllowed{"Allowed under endpoint
enforcement mode?"} + PostMwAllowed -- No --> PostMwDeny["Local policy deny
no credential injection"] + PostMwAllowed -- Yes --> Creds + Creds --> Rewrite["Inject credentials into configured slots"] + Rewrite --> HttpDial["Connect or reuse upstream"] + HttpDial --> HttpResponse["Write request and relay response"] + HttpResponse --> Upgrade{"101 WebSocket upgrade?"} + Upgrade -- No --> NextReq{"Another HTTP request
on this connection?"} + NextReq -- Yes --> HttpReq + NextReq -- No --> Done["HTTP relay done"] + Upgrade -- Yes --> WsInspect{"WebSocket inspection
or rewrite configured?"} + WsInspect -- No --> RawUpgrade["Raw upgraded stream"] + WsInspect -- Yes --> WsRelay["WebSocket relay
text-frame rewrite / message policy"] + end +``` + +Read this as two phases. The top half chooses the relay shape from the adapter +surface and endpoint enforcement. The `HTTP relay request loop` only receives a +parsed HTTP request. Supervisor middleware is not another policy funnel; it is +an optional request-path hook after HTTP policy allows the request and before +OpenShell-managed credential injection. When middleware changes a request +body, the relay re-parses body-dependent protocol inputs and re-evaluates +request policy before credential injection or upstream write. + +Relay rules: + +- HTTP credential injection happens in both HTTP modes: L4-only HTTP and + HTTP-inspected. +- HTTP-inspected endpoints include `rest`, `graphql`, `json-rpc`, `mcp`, and + `websocket`. JSON-RPC and MCP are HTTP L7 protocols, not native TCP protocol + processors. +- Supervisor middleware is a typed relay hook. V1 middleware runs on parsed + HTTP requests at `HTTP_REQUEST / PRE_CREDENTIALS`, after network and request + policy admit the request and before OpenShell injects credentials. +- Middleware can allow, deny, replace the bounded request body, add approved + headers, and emit audit-safe findings/metadata. External middleware must not + receive OpenShell-managed credentials. +- Middleware mutation cannot bypass request policy. After a body replacement, + the relay re-parses and re-evaluates body-dependent GraphQL, JSON-RPC, and MCP + policy inputs before credential injection or upstream write. A policy + mismatch follows the endpoint's configured enforcement mode; a malformed or + unclassifiable transformed protocol body fails closed even in audit mode. +- Credential injection includes static placeholder rewrite and endpoint-bound + dynamic token grants. Token grants run after policy allow and before upstream + write; failures deny without forwarding the request. +- Middleware-transformed content must not create a new path for resolving + OpenShell credential placeholders unless the middleware hook is explicitly + trusted as credential-capable. The safe default is to fail closed on newly + introduced reserved placeholders before credential injection. +- Static credential rewrite covers request target, query, headers, opt-in REST + request bodies, and opt-in client-to-server WebSocket text frames. +- HTTP L7 policy is evaluated before upstream write for each request. JSON-RPC + and MCP evaluation parse bounded JSON-RPC-over-HTTP bodies; MCP adds + tool-aware selectors for `tools/call`. +- WebSocket upgrade policy is evaluated as HTTP first. After an allowed `101` + upgrade, the WebSocket relay owns frame parsing when text-frame credential + rewrite, WebSocket transport policy, GraphQL-over-WebSocket policy, or safe + compression handling is configured. Other upgraded streams remain raw. +- Forward HTTP must stay in the shared HTTP relay loop or in an equivalent + guarded single-request relay. It must not evaluate one request and then + switch to raw bidirectional copy. +- `protocol: tcp` or an omitted protocol means L4 authorization plus byte copy, + except that HTTP-looking streams may still use HTTP credential injection. +- Future native protocol processors, such as Redis, Postgres, or MySQL, own the + full message loop and can parse multiple commands or queries on one TCP + session. A processor may be in-tree, middleware-backed, or a combination + where in-tree framing exposes typed middleware hooks. + +### Adapter Responsibilities + +CONNECT remains the generic explicit proxy mode for HTTPS and arbitrary TCP. +The CONNECT adapter parses `CONNECT host:port` into an `EgressIntent`, asks the +shared authorization boundary for an `EgressDecision`, returns the tunnel-ready +response only after the connection is allowed, and then hands the tunnel to the +relay. The upstream connection is opened by the HTTP relay or protocol +processor when payload policy allows it. The compatibility milestone preserves +the current raw-relay dial point until the processor boundary exists. + +Forward HTTP is compatibility for clients that send absolute-form HTTP +requests. The adapter parses the first request, rewrites proxy framing only at +the relay boundary, rejects `https://` absolute-form requests, rejects +unsupported h2c upgrades on inspected routes, and either stays in a shared HTTP +request loop or forces `Connection: close` for a guarded single request. + +Transparent TCP is for native clients that do not know they are using a proxy. +It depends on policy DNS and nftables capture. For a policy-eligible native TCP +name, policy DNS returns a supervisor-owned synthetic IP and creates an active +mapping from that IP to the normalized name, matched endpoint, allowed ports, +and validated real addresses. Userland later calls +`connect(synthetic_ip:port)`, nftables redirects the traffic to a userland +listener, and the TCP adapter recovers the synthetic destination and exact +mapping before building an intent. + +Policy DNS replaces static `/etc/hosts` snapshots for native TCP names. It is +query-driven. It first checks whether the normalized name matches an endpoint +whose transport and protocol contract enables native TCP through policy DNS. A +name without such an endpoint receives a local policy-denial DNS response, +normally `REFUSED`, and is never sent to upstream DNS. An eligible name is +resolved through trusted DNS, and every returned address is filtered through +destination and SSRF controls before the adapter atomically publishes the +mapping and capture state and returns the synthetic IP to userland. + +The later connect still runs through normal authorization. The connector may +dial only the validated real addresses pinned in the unexpired mapping; it must +not perform an unrelated fresh resolution or treat a direct connection to one +of those real IPs as correlated. Process identity remains independent +authorization evidence evaluated at connect time, not the mechanism that joins +the DNS request to the TCP connection. + +Local service adapters stay outside the normal external egress relay: +`inference.local` routes chat, completion, model discovery, embeddings, and +provider-specific inference traffic through the router with local limits; +`policy.local` exposes current policy, denial summaries, proposal submission, +and proposal wait routes; metadata loopback serves provider metadata +credentials to SDKs that bypass HTTP proxy variables. + +### Network Enforcement Substrate + +Current main uses nftables for sandbox bypass enforcement. It accepts +proxy-bound traffic, loopback, and established flows, then rejects and +optionally logs other TCP/UDP traffic for the bypass monitor. That is current +enforcement, not native TCP capture. + +```mermaid +flowchart TD + Packet["Userland packet"] --> ProxyDest{"Proxy destination?"} + ProxyDest -- Yes --> AcceptProxy["nftables accept"] + ProxyDest -- No --> Capture{"Active synthetic-IP
capture match?"} + Capture -- Yes --> Redirect["nftables redirect/TPROXY to transparent adapter"] + Capture -- No --> Reject["nftables log + reject bypass"] + Reject --> Monitor["Bypass monitor emits OCSF"] +``` + +Transparent TCP extends this nftables model with explicit capture rules that +run before the reject path and are scoped to unexpired synthetic-IP mappings. +The sandbox resolver points to policy DNS, while direct external DNS traffic +continues to the reject path. DNS-over-HTTPS is ordinary HTTPS egress and +requires its own allowed endpoint. Transparent capture does not add a parallel +iptables path. The compatibility milestone leaves the current table unchanged; +capture arrives in a later feature phase. + +### Deployment Modes + +| Mode | Shape | Status | +|------|-------|--------| +| Embedded supervisor | `openshell-sandbox` orchestrates `openshell-supervisor-network` and `openshell-supervisor-process` | Current | +| Network-only supervisor | Networking, policy, proxy, local services, and background tasks run without a payload process leaf | Current runtime mode | +| Standalone proxy binary | Supervisor launches networking as a separate process with explicit APIs | Future packaging/API work | +| Sidecar proxy | Proxy runs outside the payload container but inside the sandbox boundary | Future isolation mode | + +A pluggable proxy must expose the right userland surfaces, implement the +gateway APIs it needs, and prove equivalent policy enforcement through tests. +If supervisor middleware is configured, the proxy runtime must also receive the +effective middleware service registry, validate and refresh bindings, enforce +`fail_open` and `fail_closed`, buffer within configured caps, invoke middleware +on the request path, and emit middleware OCSF events. + +Process identity is mode-dependent. Embedded supervisor mode normally requires +successful workload process, binary, and ancestor resolution; a lookup failure +continues to deny. A trusted runtime can explicitly select the existing +endpoint-only mode, in which identity is recorded as intentionally unavailable +and policy evaluation keeps its current endpoint-only semantics. The refactor +must not represent either case as a fabricated empty identity or accidentally +convert a lookup failure into endpoint-only evaluation. + +Future standalone and sidecar modes must advertise identity capability. A mode +without local identity needs an explicit unavailable-identity contract and +policy validation for binary/path predicates; it does not automatically inherit +endpoint-only semantics. The nftables rules that force, capture, or reject +userland traffic remain owned by the sandbox network boundary even if the proxy +process later moves into a standalone binary or sidecar. + +### Migration And Operational Contract + +Mechanical adapter, destination, and relay extractions ship as isolated, +revertible changes without a permanent feature flag. The deterministic +authorization result is different: it first runs beside the legacy queries in +shadow mode, reports audit-safe mismatches through internal telemetry, and +retains the legacy evaluator through the cutover observation window. + +Policy DNS, transparent TCP, native processors, and alternate deployment modes +land only after the shared authorization and relay contracts are authoritative. +Each is a separate, feature-bearing pull request or series with its own +capability gating, migration, telemetry, and rollback plan; they are not bundled +into the compatibility-only refactor. + +Adapter response bytes and OCSF event class, action, disposition, severity, +status, destination, actor, firewall rule, message, and status detail are +compatibility surfaces. Moving code does not justify changing them. Performance +is also measured at each phase; fewer OPA evaluations are a target to verify, +not an unmeasured claim. + +## Implementation plan + +The detailed migration plan lives in [implementation-plan.md](implementation-plan.md). +The intended order is: + +1. Lock down current responses, OCSF events, policy outcomes, credential + behavior, upstream-dial timing, and local-service behavior with regression + coverage. +2. Introduce compatibility `EgressIntent` and `EgressDecision` envelopes while + preserving current lookup precedence and failure defaults. +3. Centralize destination validation behind an unopened connector. +4. Materialize one generation-consistent authorization decision, compare it + against the legacy queries, then cut over deterministic endpoint selection + and fail-closed metadata handling as an independently reviewable step. +5. Consolidate HTTP request-loop, credential, WebSocket, and middleware relay + behavior in separately shippable subphases. +6. Consolidate TLS handling and existing raw TCP relay selection. +7. Preserve current local-service boundaries and remove compatibility plumbing. +8. Add the native protocol-processor dispatch contract, then add protocol + implementations as independently reviewed features. +9. Add policy DNS state and transparent TCP capture with mandatory + DNS-answer-to-connect correlation and separate mapping generations. +10. Define capability-checked standalone or sidecar runtime contracts and + complete cleanup after each boundary is in use. + +Steps 1 through 7 are the compatibility foundation and may themselves span +several pull requests. Steps 8 through 10 are later feature milestones; their +presence in this RFC defines the direction without adding them to the initial +refactor branch. + +## Risks + +- Tightening L7 and TLS metadata failures from fail-open to deny may expose + latent policy or Rego errors. `allowed_ips` and SSRF validation already fail + more conservatively; tests must cover each query independently. +- Deterministic endpoint selection may change ambiguous overlapping policies. + The new decision must shadow the legacy queries and report mismatches before + any semantic cutover. +- Token grants add a runtime dependency on SPIFFE Workload API and token + endpoints. Failures should remain fail-closed and sanitized. +- Transparent TCP capture adds network-namespace interception and mutable DNS + mapping state. Synthetic address allocation must coexist with runtime + networks, avoid premature reuse, prevent unrelated bare-IP connections from + inheriting DNS authorization, and fail closed across policy or mapping + generation changes. +- Sidecar or standalone modes may intentionally lack process identity. + Binary/path-scoped policy needs an advertised identity capability and policy + validation; missing identity cannot silently broaden an allow. +- Metadata loopback and `policy.local` expand sandbox-local control surfaces + and need strict route validation, body limits, redaction, and authentication + boundaries. +- Provider-composed policy rules use a reserved namespace. Decisions and logs + must distinguish provider-derived policy from user-authored policy without + exposing provider rules as editable sandbox proposals. +- Supervisor middleware adds a synchronous request-path dependency. Body caps, + timeout behavior, registry reloads, and `fail_open` choices must be visible + in telemetry so operators can diagnose whether content inspection ran. +- Moving OCSF emission sites can accidentally change event class, action, + disposition, severity, message, or actor/destination fields. Adapter response + shapes and OCSF schemas are compatibility requirements, not cleanup targets. +- New decision/context objects add per-connection allocations on a hot path. + Performance claims require before/after measurements of OPA evaluation count, + allocation volume, and connection/request latency. +- Structural phases back out by reverting their isolated commits. The + deterministic-decision cutover must retain the legacy evaluator long enough + for shadow comparison and immediate rollback; a permanent feature flag is not + required for the purely mechanical phases. + +## Alternatives + +### Keep patching each entry path + +This has the lowest short-term cost but keeps security behavior duplicated +across CONNECT, forward HTTP, and local services. It also makes future TCP +application protocol support harder because each parser must be wired through +multiple entry mechanisms. + +### Replace CONNECT with forward proxy + +Forward proxy only covers plaintext absolute-form HTTP requests. It is not a +replacement for HTTPS tunnels, WebSocket tunnels, or arbitrary TCP clients. +CONNECT should remain the generic explicit proxy mode. + +### Build only transparent TCP + +Transparent TCP helps native clients but does not replace explicit proxy +support used by common HTTP tooling. It also requires the shared authorization, +destination, and relay boundaries plus policy DNS and nftables capture before +it can safely preserve endpoint identity. For that reason it is a later phase +of this RFC, not the first implementation change. + +### Return real addresses from policy DNS + +Returning validated real addresses avoids a synthetic address pool, but the +later TCP connection carries only an IP and port. Two policy names can share +the same real address and port, and a direct bare-IP connection is +indistinguishable from one caused by the earlier lookup. Process identity does +not solve shared resolvers, caches, cross-process handoff, or two names resolved +by the same process. The proposal therefore uses a synthetic address as the +correlation handle and keeps process identity as separate authorization +evidence. + +## Prior art + +The current `openshell-supervisor-network` split is the immediate prior step: +it already separates proxy, OPA, L7, inference routing, policy-local routes, +TLS, and token grants from process supervision. + +The current `openshell-supervisor-process` netns and bypass monitor are the +packet-enforcement substrate. Transparent TCP extends that nftables model in a +later phase rather than creating a second firewall path. + +The existing L7 relay is the behavioral prior art for this RFC. It already +proves per-request HTTP evaluation, GraphQL parsing, JSON-RPC/MCP body +inspection, WebSocket frame handling, request-body rewrite, and token-grant +injection can live behind relay boundaries. + +RFC 0009 supervisor middleware is the extension prior art. It defines +`HTTP_REQUEST / PRE_CREDENTIALS` as a supervisor-owned hook that can inspect, +deny, or transform admitted HTTP requests before credentials are injected. RFC +0005 should place that hook inside the shared relay rather than making each +adapter wire middleware separately. + +## Open questions + +1. Should overlapping endpoint metadata be rejected at policy load time, or + should one documented policy/endpoint precedence key select the complete + decision? The initial compatibility refactor does not choose between them. +2. What mismatch-free observation window is sufficient before the + deterministic decision replaces the legacy endpoint queries? +3. Should metadata loopback be modeled as an adapter inside + `openshell-supervisor-network`, or remain orchestrated by `openshell-sandbox` + with shared credential/provider helpers? +4. What TTL cap should policy DNS use, and should policy reload immediately + invalidate all active mappings or permit a bounded drain period that cannot + authorize new connections? +5. Which non-routable synthetic IPv4 and IPv6 ranges can each runtime reserve, + and what quarantine period prevents address reuse while stale DNS answers + may remain cached? +6. Which original-destination mechanism and nftables redirect mode should each + supported runtime use while keeping capture rules ahead of bypass rejection? +7. Which identity capabilities must standalone and sidecar runtimes advertise + before the gateway accepts binary/path-scoped policy for them? diff --git a/rfc/0005-sandbox-proxy-egress-adapter/current-shape.md b/rfc/0005-sandbox-proxy-egress-adapter/current-shape.md new file mode 100644 index 000000000..340c7933c --- /dev/null +++ b/rfc/0005-sandbox-proxy-egress-adapter/current-shape.md @@ -0,0 +1,286 @@ +# Current Shape Appendix + +This appendix records the current proxy shape and the review findings that +motivate the adapter model. The main RFC intentionally keeps these details out +of the direction document. + +## Current Runtime Split + +The proxy is no longer only a large module inside `openshell-sandbox`. +Current main has three relevant runtime owners: + +```mermaid +flowchart TD + Sandbox["openshell-sandbox
orchestrator"] + Network["openshell-supervisor-network
proxy, OPA, L7, TLS, inference,
policy.local, token grants"] + Process["openshell-supervisor-process
process leaf, SSH, netns,
nftables, bypass monitor"] + Denials["Denial/activity aggregators"] + Gateway["Gateway policy/provider APIs"] + + Sandbox --> Network + Sandbox --> Process + Network --> Denials + Process --> Denials + Sandbox --> Gateway + Network --> Gateway +``` + +`openshell-sandbox` creates the shared network namespace, owns denial/activity +channels, starts the policy poll loop, starts networking, starts the metadata +loopback server when needed, and then optionally starts the process leaf. If +`process_enabled` is false, the supervisor can run in network-only mode and +keep networking/background tasks alive until shutdown. + +`openshell-supervisor-network` owns the explicit proxy listener, OPA engine +integration, L7 enforcement, TLS termination, inference routing, policy-local +routes, identity cache, provider credential injection, and token grants. + +`openshell-supervisor-process` owns process execution, SSH, network namespace +helpers, nftables bypass rules, and the bypass monitor that turns nftables LOG +entries into OCSF events. + +In embedded supervisor mode, the network leaf normally uses process metadata +resolved by the process/orchestrator side for binary-scoped policy and OCSF +context. Current runtime configuration can instead select endpoint-only policy +evaluation. The adapter model must preserve that intentional mode separately +from an identity lookup failure; the latter continues to deny when binary +identity is required. + +## Current Userland-Facing Surfaces + +The networking surface currently includes: + +- CONNECT proxy traffic for HTTPS and generic TCP tunnels. +- Forward HTTP proxy traffic for absolute-form HTTP requests. +- `inference.local` for local inference routing. +- `policy.local` for current policy, denial summaries, proposal submission, + and proposal wait routes. +- GCE metadata loopback for SDKs that bypass HTTP proxy variables. +- nftables bypass enforcement for direct TCP/UDP egress that does not enter + the proxy. +- OPA/Rego policy and endpoint metadata lookups. +- DNS resolution and endpoint validation for CONNECT and forward HTTP egress. +- Static provider credential injection and redaction. +- Endpoint-bound dynamic token grant injection. +- Opt-in REST request-body credential rewrite. +- L7 REST, GraphQL, JSON-RPC, MCP, WebSocket, and + GraphQL-over-WebSocket enforcement. + +The issue is not that these features exist. The issue is that entry mechanisms, +policy evaluation, endpoint metadata lookup, credential injection, and byte +relay decisions are still interleaved. + +## Current CONNECT Shape + +```mermaid +flowchart TD + Client["Client CONNECT host:port"] --> Parse["Parse CONNECT target"] + Parse --> L4["Evaluate network policy"] + L4 --> Allowed{"Allowed?"} + Allowed -- No --> Deny["CONNECT denial"] + Allowed -- Yes --> Meta["Query endpoint metadata"] + Meta --> Config{"L7, TLS, or credential config?"} + Config -- No --> Tunnel["Return tunnel-ready response"] + Config -- Yes --> Tunnel + Tunnel --> Inspect["Inspect tunneled bytes when possible"] + Inspect --> Relay["HTTP/WebSocket/TCP relay selection"] + Relay --> Inject["Middleware, static credentials, and token grants if configured"] + Inject --> Upstream["Open upstream when relay policy allows"] +``` + +CONNECT is still the strongest entry shape because the tunnel relay can keep +parsing HTTP requests on long-lived connections and enforce request policy per +request. + +## Current Forward HTTP Shape + +```mermaid +flowchart TD + Client["Absolute-form HTTP request"] --> Parse["Parse first request"] + Parse --> L4["Evaluate network policy"] + L4 --> Allowed{"Allowed?"} + Allowed -- No --> Deny["HTTP denial"] + Allowed -- Yes --> L7{"Matching L7 endpoint?"} + L7 -- Yes --> Eval["Evaluate REST/GraphQL/JSON-RPC/MCP/WebSocket policy"] + Eval --> Guard["Reject unsupported h2c upgrade when inspected"] + Guard --> Rewrite["Rewrite to origin-form + configured credentials"] + L7 -- No --> Rewrite + Rewrite --> Token["Apply token grant if endpoint-bound"] + Token --> Close["Force Connection: close except WebSocket upgrade"] + Close --> Upstream["Open upstream"] + Upstream --> Relay["Guarded HTTP relay / upgrade relay"] +``` + +Latest main no longer has the old raw-copy-after-first-request shape for +ordinary forward HTTP. It rewrites ordinary requests with `Connection: close`, +uses guarded HTTP relay helpers for body handling, rejects inspected h2c +upgrades, injects token grants, and sends allowed WebSocket upgrades through +the upgrade relay. That is a narrower surface than the historical bidirectional +copy, but it is still orchestrated separately from the CONNECT relay path. + +## Current Local Service Shape + +```mermaid +flowchart TD + Request["Request to local name"] --> Match{"Known local route?"} + Match -- "inference.local" --> Inference["Inference route adapter"] + Match -- "policy.local" --> Policy["Policy local adapter"] + Match -- "metadata loopback" --> Metadata["Metadata credential server"] + Match -- No --> External["Normal egress path"] + Inference --> InferenceResp["Local inference response"] + Policy --> PolicyResp["Local policy response"] + Metadata --> MetadataResp["Metadata response"] +``` + +`inference.local` now covers buffered and streaming inference shapes including +chat/completion routes, model discovery, embeddings, and provider-specific +routes. `policy.local` supports the agentic approval loop: agents can submit +narrow proposals and wait on approval/reload before retrying. Metadata +loopback exists for provider credentials consumed by SDKs that do not honor +HTTP proxy variables. + +These are userland-facing network surfaces. They should stay distinct from +external egress while still fitting the adapter model. + +## Adjacent In-Flight Supervisor Middleware Shape + +PRs #1738 and #2027 propose supervisor middleware as an HTTP request hook in +the proxy relay. That work is adjacent to this RFC rather than a separate entry +adapter. + +```mermaid +flowchart TD + Req["Parsed admitted HTTP request"] --> Policy["Network and request policy already allowed"] + Policy --> Hook["HTTP_REQUEST / PRE_CREDENTIALS middleware"] + Hook --> Outcome{"Middleware outcome"} + Outcome -- "deny" --> Deny["Local deny, no credential injection"] + Outcome -- "allow / mutate" --> Recheck["Re-parse transformed body and re-evaluate policy"] + Recheck --> Allowed{"Allowed under endpoint enforcement mode?"} + Allowed -- "no" --> PolicyDeny["Local policy deny, no credential injection"] + Allowed -- "yes" --> Creds["Credential injection"] + Creds --> Upstream["Upstream write"] +``` + +The proposed middleware chain is selected by admitted destination host, runs in +deterministic order, buffers bounded request bodies, applies `fail_open` or +`fail_closed`, emits audit-safe findings, and runs before OpenShell-managed +credentials are injected. Middleware-transformed GraphQL, JSON-RPC, and MCP +bodies are re-parsed and re-evaluated before credential injection or upstream +write, so a mutation cannot bypass request policy. Policy mismatches retain the +endpoint's audit or enforce behavior, while malformed transformed protocol +bodies fail closed in either mode. Middleware can inspect WebSocket upgrade +requests because they are HTTP requests, but it does not inspect post-upgrade +WebSocket frames in v1. + +RFC 0005 should account for this by treating middleware as part of the shared +request processing plan. CONNECT and forward HTTP should not each learn how to +select and invoke middleware independently. + +## Current Network Namespace Enforcement + +```mermaid +flowchart TD + Start["Process in sandbox network namespace"] --> Dest{"Destination"} + Dest -- "Proxy host_ip:port" --> Proxy["Accept to sandbox proxy"] + Dest -- "Loopback" --> Loopback["Accept loopback"] + Dest -- "Established/related" --> Established["Accept response packet"] + Dest -- "Other TCP/UDP" --> Reject["nftables log + reject"] + Reject --> Monitor["Bypass monitor reads dmesg"] + Monitor --> OCSF["OCSF network + detection events"] +``` + +The process leaf installs an `inet` nftables filter table for bypass +enforcement. The table accepts proxy-bound traffic, loopback, and established +flows, then rejects and optionally logs other TCP/UDP traffic. It does not +currently redirect native TCP connections into the proxy. + +## Findings To Preserve + +### Invariant: forward proxy must not relay unevaluated follow-on HTTP bytes + +The historical forward path evaluated at most the first absolute-form request, +rewrote it, then switched to bidirectional copy. Bytes already buffered after +the first header block, or later pipelined requests on the same client/upstream +connection, could reach upstream without the CONNECT L7 relay's per-request +parser/evaluator. + +Latest main mitigates this by forcing ordinary forward HTTP to one request per +connection and by using guarded relay helpers. The adapter model should +preserve the invariant either by keeping forward HTTP single-request/close or +by passing the first parsed request into a shared HTTP relay loop. + +### Endpoint config is not tied to deterministic matched policy + +The policy name used for L4 authorization and logging is the lexicographically +smallest matching policy. L7 candidates are collected independently and later +selected by request-path specificity. TLS and `allowed_ips` use the first +extended endpoint config returned by a separate query. Exact-declared-host is +another independent existential query. With overlapping host, port, and binary +rules, those results can describe different endpoints and policies on the same +connection. + +The adapter model requires authorization to return one decision with one +deterministic matched endpoint. + +### Policy materialization can span generations + +The L4 decision, L7 route, TLS mode, `allowed_ips`, and exact-declared-host +signal are materialized through separate engine calls. The L7 route records a +generation for its tunnel evaluator, but the sibling metadata queries are not +all asserted against the L4 decision generation. A reload during setup can +therefore assemble one logical connection decision from different policy +generations. + +The target decision carries one top-level policy generation. Every +policy-derived field must be evaluated from that generation, and relay startup +must reject a stale decision before an upstream request is written. + +### Endpoint metadata query failures should not erase enforcement + +Failure behavior is not uniform today. L7 configuration failure becomes no L7 +configuration, and TLS configuration failure becomes automatic TLS handling; +either can erase intended enforcement. `allowed_ips` and the downstream SSRF +validation path are already more conservative. The migration must test each +query independently instead of describing all metadata failures as equivalent. + +The adapter model treats endpoint metadata as part of the authorization result. +Failure to materialize required metadata should deny rather than erase extended +configuration. + +### Destination validation must be shared + +Private address checks, `allowed_ips`, exact declared private endpoint trust, +trusted gateway aliases, SSRF checks, and control-plane port blocks have grown +over time. They should be centralized so CONNECT and forward HTTP use the same +resolved-destination rules. Existing local services remain outside normal +external destination validation. + +## Existing Feature Inventory + +The refactor should preserve: + +- CONNECT explicit proxy support. +- Forward HTTP explicit proxy support. +- Network-only supervisor mode. +- nftables bypass reject/log enforcement. +- Provider credential injection and redaction. +- Dynamic token grant injection through SPIFFE-backed provider credentials. +- Supervisor middleware `HTTP_REQUEST / PRE_CREDENTIALS` when it lands. +- REST request-body credential rewrite. +- WebSocket text-frame credential rewrite. +- REST endpoint method/path policy. +- GraphQL-over-HTTP policy. +- JSON-RPC-over-HTTP method policy. +- MCP Streamable HTTP method and tool policy. +- WebSocket transport and GraphQL-over-WebSocket policy. +- h2c rejection on inspected HTTP routes. +- Inference routing through `inference.local`, including embeddings. +- Agent-facing policy advisor routes through `policy.local`. +- GCE metadata loopback for supported provider credentials. +- Timeout and resource tracking for client, upstream, and local service work. +- Structured OCSF logging for network and HTTP policy outcomes. +- SSRF and internal address protections. +- Exact declared private endpoint handling. +- Control-plane port protection. +- `allowed_ips` endpoint restrictions. +- TLS auto-detection and termination for inspectable client connections. diff --git a/rfc/0005-sandbox-proxy-egress-adapter/implementation-plan.md b/rfc/0005-sandbox-proxy-egress-adapter/implementation-plan.md new file mode 100644 index 000000000..5e8a06b74 --- /dev/null +++ b/rfc/0005-sandbox-proxy-egress-adapter/implementation-plan.md @@ -0,0 +1,279 @@ +# Implementation Plan + +This plan is intentionally separate from the main RFC so the proposal can stay +direction-focused. The RFC is an incremental roadmap, not one pull request. +Phases 0 through 7 form the compatibility foundation: they restructure current +CONNECT, forward HTTP, raw TCP, and local-service behavior without adding a new +user-facing transport. Phases 8 and later add the forward-looking capabilities +after the shared contracts are authoritative. + +## Phase 0 - Compatibility Baseline + +- Cover CONNECT and forward HTTP allow/deny responses, including exact status, + headers, and adapter-specific error bodies. +- Cover forward HTTP pipelining, keep-alive follow-on requests, the current + `Connection: close` mitigation, `https://` absolute-form rejection, and h2c + rejection on inspected endpoints. +- Cover the current overlapping-policy outcomes separately for matched policy, + L7 route selection, TLS, `allowed_ips`, and exact-declared-host. +- Inject failures into L7, TLS, `allowed_ips`, and exact-declared-host queries. + Record the current fail-open or fail-closed result for each query rather than + treating all endpoint metadata errors as equivalent. +- Cover control-plane ports, cloud metadata, always-blocked addresses, exact + declared private endpoints, IP-literal synthesis, trusted gateway aliases, + and explicit `allowed_ips` through CONNECT and forward HTTP. +- Cover identity-required success/failure, unsupported-platform behavior where + possible, and intentional endpoint-only evaluation. Prove an empty + `exec.path` cannot satisfy binary-scoped policy while identity is required. +- Cover static credential injection, token grants, REST body rewrite, + WebSocket text-frame rewrite and policy, GraphQL, JSON-RPC, and MCP behavior. +- Cover `inference.local`, `policy.local`, metadata loopback, and unchanged + nftables bypass reject/log behavior. +- Capture stable OCSF event class, activity/action/disposition, severity, + status, destination, actor, firewall rule, message, and status detail for + representative allow and deny paths. +- Record a performance baseline for OPA evaluations, per-connection + allocations, and CONNECT/forward request latency. + +## Phase 1 - Adapters And Compatibility Decision Envelope + +- Introduce CONNECT and forward HTTP `EgressIntent` construction inside + `openshell-supervisor-network`. +- Introduce a transitional `EgressDecision` carrying L4 outcome, policy + generation, process evidence, and endpoint fields while preserving the + current query timing, precedence, and failure defaults. +- Keep `LookupFailed`/unsupported identity as a denial when identity is + required. Keep explicitly configured endpoint-only mode behavior unchanged. +- Keep adapter-specific responses and OCSF emission at the protocol boundary. +- Do not claim the transitional decision is one atomic OPA result; document its + compatibility hydration until Phase 3 cuts over. + +This phase is a mechanical extraction. It must be independently shippable and +revertible without changing user-visible policy or relay behavior. + +## Phase 2 - Shared Destination Validation + +- Move DNS resolution, explicit `allowed_ips`, exact declared endpoints, + implicit IP-literal handling, trusted gateway aliases, SSRF checks, + cloud-metadata blocks, and control-plane-port blocks into one validator. +- Represent the selected validation mode explicitly instead of passing an + ambiguous collection of booleans. +- Return an unopened `UpstreamConnector` so adapters and relays preserve the + current point at which upstream TCP is created. +- Prove CONNECT and forward HTTP retain their existing denial responses, OCSF + fields, and dial timing while using the shared validator. + +## Phase 3 - Generation-Consistent Authorization Cutover + +- Define either rejection of ambiguous overlapping endpoint metadata or one + documented policy/endpoint precedence key before changing enforcement. +- Add one OPA result that materializes matched policy/source, matched endpoint, + destination constraints, TLS, HTTP enforcement, credential plan, and + middleware selection from one policy generation. +- Attach a generation-pinned `TunnelPolicyEngine` to relay context for + per-request REST, GraphQL, JSON-RPC, MCP, and WebSocket evaluation. Relays do + not rematerialize connection-level endpoint policy. +- Run the new query in shadow mode beside legacy queries. Emit internal, + audit-safe mismatch telemetry without changing existing OCSF network/HTTP + events or enforcement. +- Add reload-race tests proving every materialized field matches the top-level + generation and stale decisions stop before upstream request write. +- Make deterministic selection and fail-closed L7/TLS metadata errors a + dedicated cutover only after mismatch cases are understood. Retain the + legacy evaluator temporarily for immediate rollback. + +This is the only phase that intentionally tightens ambiguous or error behavior; +it must not be hidden inside the structural refactor commits. + +## Phase 4 - Forward HTTP Adapter + +- Keep absolute-form parsing and adapter-specific errors at the forward HTTP + boundary. +- Pass the buffered first request into a shared HTTP relay, or retain the + guarded single-request/`Connection: close` path until Phase 5a is ready. +- Preserve `https://` absolute-form rejection and inspected h2c rejection. +- Preserve the invariant that no unevaluated follow-on request can reach raw + bidirectional copy. + +## Phase 5 - Relay Consolidation + +### Phase 5a - HTTP request loop + +- Centralize HTTP parsing and per-request REST, GraphQL, JSON-RPC, and MCP + evaluation behind the generation-pinned request-policy handle. +- Evaluate every request before upstream write and preserve the current rule + that a denied request does not create an upstream session. +- Preserve bounded JSON-RPC/MCP inspection and audit-safe logging that omits + params and tool arguments. + +### Phase 5b - Credential injection + +- Unify static target/query/header rewrite, endpoint-bound token grants, and + opt-in REST request-body rewrite after request allow and before upstream + write. +- Preserve buffering limits, supported content types, `Content-Length` + recomputation, redaction, token caching, and fail-closed unresolved secrets. + +### Phase 5c - WebSocket + +- Move allowed upgrades behind the shared relay while preserving raw upgraded + passthrough, opt-in text-frame credential rewrite, WebSocket transport policy, + GraphQL-over-WebSocket policy, and safe compression behavior. + +### Phase 5d - Supervisor middleware + +- Land only after the supervisor middleware dependency is available. +- Run `HTTP_REQUEST / PRE_CREDENTIALS` after request allow and before static or + dynamic credential injection. +- Re-parse middleware-transformed bodies and re-evaluate GraphQL, JSON-RPC, and + MCP policy inputs before credential injection or upstream write. Preserve the + endpoint's audit or enforce behavior for policy mismatches, and fail closed + on malformed transformed protocol bodies in either mode. +- Preserve ordering, body caps, `fail_open`/`fail_closed`, safe headers, + findings, metadata, and rejection of middleware-introduced credential + placeholders. +- Test allowed requests that middleware rewrites into denied GraphQL, JSON-RPC, + and MCP operations, including audit-mode forwarding and fail-closed malformed + replacements. + +Each subphase must be independently testable and shippable; Phase 5 is not a +single flag-day cutover. + +## Phase 6 - Shared TLS And TCP Relay Boundary + +- Move client-side TLS detection and termination before the HTTP/raw-TCP relay + split without changing handshake, certificate, or upstream-connect timing. +- Keep endpoint TLS behavior on `EgressDecision` and preserve `tls: skip` as the + explicit raw-tunnel path. +- Use one existing raw `TcpRelay` byte-copy primitive for L4 traffic. +- Add a protocol-processor dispatch contract without enabling a concrete new + protocol in the compatibility milestone. +- Let processors own their message loop and call the validated connector only + when protocol state allows. Permit in-tree, middleware-backed, and hybrid + processors with typed middleware operations. + +## Phase 7 - Existing Local Services And Cleanup + +- Keep `inference.local` as a local adapter with its existing TLS, route, + provider-auth, streaming/buffered limit, and OCSF behavior. +- Keep `policy.local` as a local adapter for current policy, bounded denial + summaries, proposals, and proposal wait. +- Decide whether metadata loopback remains orchestrated by `openshell-sandbox` + or moves behind a local adapter boundary; preserve startup/failure behavior + either way. +- Keep the local-routing and destination contracts extensible for issue + [#1633](https://github.com/NVIDIA/OpenShell/issues/1633), while leaving its + policy surface and host-loopback authorization to separate feature work. +- Remove compatibility endpoint queries only after Phase 3 is authoritative. +- Remove duplicated destination/relay plumbing without centralizing + adapter-specific response rendering. +- Update the living architecture documentation once each implemented boundary + reflects current code. + +Completion of Phase 7 is the compatibility milestone: existing user-facing +features and capabilities are preserved on the new internal structure. The +following phases are feature-bearing work and land in separate pull requests or +series. + +## Phase 8 - Policy DNS And Transparent TCP + +- Add policy DNS registration for native TCP endpoint names. +- Reject names that do not match an eligible native TCP endpoint before making + an upstream DNS query. +- Replace static host-file mapping with query-driven synthetic DNS answers. + Resolve eligible names through trusted DNS and filter every real address + through destination controls. +- Allocate a supervisor-owned synthetic IP and store the normalized name, + endpoint ID, allowed ports, validated real addresses, policy generation, + distinct DNS mapping generation, mapping ID, and expiration in active mapping + state. +- Require every captured connect to correlate with the unexpired mapping + selected by its synthetic destination and requested port. Do not allow + unrelated bare-IP traffic to inherit a policy-DNS decision. +- Publish mapping and nftables capture updates atomically from the adapter's + perspective before returning the synthetic DNS answer. +- Add nftables REDIRECT/TPROXY capture rules ahead of the bypass reject path; + do not add a parallel iptables path. +- Coordinate capture-rule ownership with + `openshell-supervisor-process::netns` and preserve reject/log fallback for + unmatched traffic. +- Recover the original destination, construct a transparent-TCP intent, and run + normal generation-consistent authorization and destination validation. +- Restrict the connector to the mapping's pinned validated real addresses; do + not independently re-resolve at connect time. +- Keep direct external DNS blocked and treat DNS-over-HTTPS as ordinary + policy-controlled HTTPS egress. +- Define synthetic address pools and reuse quarantine, TTL caps, policy-reload + invalidation, stale-mapping behavior, and rollback before enabling capture by + default. + +## Phase 9 - Native Protocol Processors + +- Add concrete Redis, Postgres, MySQL, or other processors one protocol at a + time, each with a separately reviewed policy schema and operational limits. +- Keep omitted/`tcp` endpoints on raw L4 byte copy; never infer a native + processor from traffic alone. +- Test multi-message sessions, pre-dial denial, handshake-required dialing, + per-command/query evaluation, middleware hooks, timeouts, and redaction. +- Capability-gate policy that names a processor unavailable in the running + proxy build. + +## Phase 10 - Runtime Boundary + +- Keep embedded and network-only supervisor modes as the migration baseline. +- Define the proxy runtime API needed for a future standalone binary or + sidecar: configured listeners, policy updates, provider credentials, token + grants, middleware registry, gateway calls, telemetry, denial/activity + events, and shutdown. +- Advertise process-identity and protocol-processor capabilities. Reject policy + that requires unavailable binary/path identity or processor support. +- Represent intentional runtime identity unavailability separately from the + existing endpoint-only mode and from lookup failure. +- Add gateway capability negotiation if proxy and gateway versions can differ. + +## Phase 11 - Final Cleanup + +- Remove any compatibility query/evaluator retained for deterministic-decision + rollback after its observation window closes. +- Remove stale static `/etc/hosts`, iptables, or single-process assumptions from + proxy design and architecture documentation as the corresponding later phase + lands. +- Keep adapter-specific response rendering and OCSF contracts at their protocol + boundaries. + +## Testing And Operational Validation + +- Unit-test adapter intent construction, response rendering, explicit + destination modes, identity evidence, and authorization precedence. +- Integration-test destination validation across CONNECT and forward HTTP, + then reuse the same suite for transparent TCP when Phase 8 lands. +- Integration-test HTTP keep-alive/pipelining, REST, GraphQL, JSON-RPC, MCP, + WebSocket, credentials, token grants, middleware, and TLS/raw-TCP selection. +- Integration-test `inference.local`, `policy.local`, and metadata loopback body + limits, timeouts, redaction, and local denial responses. +- Compare OCSF fixtures before and after each migration subphase. +- Exercise policy reload between L4 decision, endpoint materialization, relay + startup, and long-lived per-request evaluation. +- Add protocol-processor harness tests before adding Redis, Postgres, MySQL, or + similar enforcement. Each concrete processor adds multi-message, handshake, + timeout, denial, redaction, and middleware coverage. +- Integration-test policy DNS filtering, denial without an upstream query, + synthetic answer allocation, TTL and reuse quarantine, distinct mapping + generations, policy-reload invalidation, atomic capture-rule updates, + original-destination recovery, allowed-port correlation, connector + restriction to pinned real addresses, and rejection of unrelated bare-IP + connects. +- Prove two names that resolve to the same real IP and port receive distinct + correlations and cannot inherit each other's endpoint policy. +- Test standalone/sidecar capability negotiation and prove missing identity or + processor support fails during policy validation rather than broadening an + allow at runtime. +- Re-run the performance baseline after the compatibility envelope, after the + single-decision query, and after relay consolidation. Treat reduced OPA calls + as a measured result rather than an assumed benefit. +- Back out structural phases by reverting their isolated commits. Keep shadow + comparison and the legacy evaluator available through the deterministic + cutover observation window. +- Gate later transport/runtime phases independently so disabling policy DNS or + transparent capture restores the existing explicit-proxy and bypass-reject + behavior without reverting the compatibility foundation. diff --git a/rfc/0005-sandbox-proxy-egress-adapter/technical-design.md b/rfc/0005-sandbox-proxy-egress-adapter/technical-design.md new file mode 100644 index 000000000..ea2767b9c --- /dev/null +++ b/rfc/0005-sandbox-proxy-egress-adapter/technical-design.md @@ -0,0 +1,594 @@ +# Technical Design Appendix + +This appendix carries implementation-level design details behind the main RFC. + +## Existing Runtime Boundary + +`openshell-supervisor-network::run::run_networking` is the current networking +startup boundary. It builds policy-local context, waits for policy binary +symlink resolution, creates the identity cache, writes the TLS CA, builds TLS +state, resolves inference routes, wires provider credentials and token grants, +and starts the proxy. The supervisor middleware work extends this boundary with +middleware registry construction and reload behavior. + +This is a useful outer boundary, but it is not yet the proxy adapter boundary. +The proxy still needs internal `EgressIntent` and `EgressDecision` boundaries +so CONNECT, forward HTTP, local routes, and future native TCP capture do not +duplicate policy and relay orchestration. The first implementation milestone +wires only current surfaces; later milestones add new adapters to the same +contract. + +## Shared Data Boundaries + +### EgressIntent + +`EgressIntent` is the normalized description of what userland is trying to do. + +It should carry: + +- entry transport: CONNECT, forward HTTP, transparent TCP, local HTTP, policy + DNS, or metadata loopback; +- requested destination host/port or captured original IP/port; +- optional process identity inputs collected by the adapter/runtime; +- optional first HTTP request for forward proxy traffic; +- optional local service route; +- policy generation and, for policy DNS/transparent TCP, a distinct DNS + mapping generation and correlation handle. + +Adapters build intents. They should not query endpoint metadata, select TLS +mode, or select relays. + +### EgressDecision + +`EgressDecision` is the policy result consumed by validation and relay code. + +It should carry: + +- allow or deny; +- one top-level policy generation used for every policy-derived field; +- deterministic matched policy identifier; +- whether the policy is user-authored, provider-derived, or local-service + internal; +- deterministic matched endpoint identifier and endpoint metadata; +- process identity availability and any identity fields used for evaluation; +- destination and allowed IP constraints; +- TLS behavior; +- protocol enforcement; +- credential injection plan; +- supervisor middleware plan; +- the request-policy selection needed to create a pinned per-request L7 + evaluator when HTTP inspection is configured; +- logging context and denial reason. + +Relay code should read this decision. It should not query OPA again for +endpoint metadata, TLS mode, allowed IPs, credential behavior, middleware +selection, or relay selection. Long-lived HTTP relays still evaluate each +request through the generation-pinned L7 evaluator carried in `RelayContext`; +that is request authorization, not endpoint rematerialization. Later native +protocol processors use the same pattern with a generation-pinned protocol +evaluator for per-command or per-query decisions. + +## Protocol Enforcement + +Use a protocol enforcement value derived from endpoint policy: + +| Policy protocol | Enforcement | Relay behavior | +|-----------------|-------------|----------------| +| omitted / `tcp` | None | L4 authorization plus byte relay, with optional HTTP sniff for credential injection | +| `rest` | HTTP | HTTP request parser with REST rules, plus opt-in request-body and WebSocket text-frame credential rewrite | +| `graphql` | HTTP | HTTP request parser with GraphQL-over-HTTP rules | +| `json-rpc` | HTTP | HTTP request parser plus bounded JSON-RPC-over-HTTP method inspection | +| `mcp` | HTTP | HTTP request parser plus bounded MCP Streamable HTTP method/tool inspection | +| `websocket` | HTTP | HTTP upgrade policy followed by WebSocket frame policy or GraphQL-over-WebSocket policy | +| future `redis`, `postgres`, `mysql`, ... | Protocol processor | Protocol-specific processor owns framing, middleware hooks, and the message loop | + +`protocol: tcp` is effectively the default L4 mode. It should not run native +protocol processors. Avoid using the term "provider" for processor concepts +because providers are already a first-class credential and routing domain in +OpenShell. Concrete native processors land after the shared dispatch contract. + +## Suggested Types + +The exact Rust shape can evolve, but the boundaries should look like this: + +```rust +enum EgressTransport { + Connect, + ForwardHttp, + TransparentTcp, + PolicyDns, + LocalHttp, + MetadataLoopback, +} + +struct EgressIntent { + transport: EgressTransport, + destination: RequestedDestination, + process: ProcessIdentityEvidence, + first_request: Option, + local_route: Option, + correlation: Option, +} + +struct EgressDecision { + policy_generation: PolicyGeneration, + outcome: PolicyOutcome, + matched_policy: Option, + endpoint: Option, + process: EvaluatedProcessIdentity, + request_processing: RequestProcessingPlan, + log_context: EgressLogContext, +} + +enum ProcessIdentityEvidence { + Available(ProcessIdentity), + Unavailable(ProcessIdentityUnavailableReason), +} + +enum ProcessIdentityUnavailableReason { + EndpointOnlyMode, + DeclaredRuntimeMode(RuntimeMode), + UnsupportedPlatform, + LookupFailed, +} + +struct EvaluatedProcessIdentity { + evidence: ProcessIdentityEvidence, + fields_used: Vec, +} + +struct MatchedPolicy { + id: PolicyId, + source: PolicySource, +} + +enum PolicySource { + User, + ProviderDerived, + LocalService, +} + +struct MatchedEndpoint { + id: EndpointId, + destination: DestinationValidationPlan, + tls: TlsPolicy, + enforcement: ProtocolEnforcement, +} + +struct DestinationValidationPlan { + address_authorization: AddressAuthorization, +} + +enum AddressAuthorization { + DefaultPublicOnly, + ExplicitAllowedIps(Vec), + ExactDeclaredHost, + ImplicitIpLiteral(IpAddr), + TrustedGatewayAlias { expected_ip: IpAddr }, +} + +struct RequestProcessingPlan { + middleware: SupervisorMiddlewarePlan, + credentials: CredentialInjectionPlan, +} + +enum ProtocolEnforcement { + None, + Http(HttpL7Config), + ProtocolProcessor(ProtocolProcessorConfig), +} + +enum HttpL7Protocol { + Rest, + Graphql, + JsonRpc, + Mcp, + Websocket, +} + +struct HttpL7Config { + protocol: HttpL7Protocol, + path: EndpointPathScope, + allow_encoded_slash: bool, + enforcement_mode: L7EnforcementMode, + websocket_credential_rewrite: bool, + request_body_credential_rewrite: bool, + websocket_graphql_policy: bool, + graphql_max_body_bytes: usize, + json_rpc_max_body_bytes: usize, + mcp_strict_tool_names: bool, +} + +struct CredentialInjectionPlan { + static_placeholders: StaticPlaceholderPlan, + token_grant: Option, +} + +struct StaticPlaceholderPlan { + http_target_query_header: bool, + rest_request_body: bool, + websocket_text_frames: bool, +} + +struct TokenGrantPlan { + provider_key: String, + auth_style: TokenGrantAuthStyle, + token_endpoint: String, +} + +struct SupervisorMiddlewarePlan { + stages: Vec, + min_body_limit: Option, + registry_generation: PolicyGeneration, +} + +struct SupervisorMiddlewareStage { + policy_name: String, + binding_id: String, + operation: MiddlewareOperation, + phase: MiddlewarePhase, + order: i32, + on_error: MiddlewareOnError, + config: MiddlewareConfig, +} + +enum MiddlewareOperation { + HttpRequest, + Future(String), +} + +enum MiddlewarePhase { + PreCredentials, + Future(String), +} + +struct RelayContext { + decision: EgressDecision, + request_policy: Option, + protocol_policy: Option, + connector: UpstreamConnector, + deadlines: RelayDeadlines, + telemetry: RelayTelemetry, +} + +struct ResolvedEndpointCorrelation { + policy_generation: PolicyGeneration, + mapping_generation: DnsMappingGeneration, + mapping_id: DnsMappingId, + synthetic_ip: IpAddr, +} + +struct PinnedRequestPolicy { + generation: PolicyGeneration, + evaluator: TunnelPolicyEngine, +} + +struct PinnedProtocolPolicy { + generation: PolicyGeneration, + evaluator: ProtocolPolicyEngine, +} +``` + +`UpstreamConnector` is the relay-owned dial boundary. It encapsulates the +validated destination and lets relays or processors open an upstream connection +only after current request or protocol policy allows it. + +`DestinationValidationPlan` selects one current validation mode. All modes +retain control-plane-port and cloud-metadata blocks. Default and explicit IP +paths retain the always-blocked loopback, link-local, and unspecified-address +checks. `ImplicitIpLiteral` is synthesized only for an explicitly declared IP +host. `TrustedGatewayAlias` may accept the one runtime-discovered gateway IP but +does not become a general private-address exemption. + +`policy_generation`, the optional pinned request/protocol evaluators, endpoint +metadata, and middleware selection must describe one policy snapshot. +Authorization asserts that every sub-materialization used that generation. If +the generation changes before relay startup, the adapter receives a +stale-policy denial rather than a mixed decision. + +## Process Identity Availability + +Process identity is evidence, not a string to fabricate when lookup fails. +Embedded mode normally populates binary, PID, ancestry, command-line path, and +binary hash data. When binary identity is required, `LookupFailed` and +`UnsupportedPlatform` remain denials. An explicitly configured endpoint-only +runtime records `Unavailable(EndpointOnlyMode)` and keeps the current +endpoint-only policy behavior. This RFC does not silently turn one state into +the other or change the endpoint-only trust contract. + +A future standalone or sidecar runtime that intentionally lacks local identity +uses `DeclaredRuntimeMode`, not `EndpointOnlyMode`, and advertises that +capability to policy validation. The runtime contract must define binary/path +predicates as unavailable and reject incompatible policy before traffic starts, +unless a later accepted policy design specifies a different fail-closed rule. + +The decision records identity availability and fields used so OCSF logs and +deny responses distinguish a binary policy denial, an identity lookup failure, +and intentional endpoint-only evaluation. Tests must prove an empty synthetic +`exec.path` cannot satisfy a binary-scoped rule while identity is required. +Adding new identity-less deployment modes, or changing how binary predicates +behave in endpoint-only mode, requires the capability work in the later runtime +phase and cannot be smuggled into the compatibility refactor. + +## Current Owners And Proposed Cleanup + +| Current owner | Current responsibility | Proposed cleanup | +|---------------|------------------------|------------------| +| `openshell-sandbox` | Orchestrator, policy poll loop, denial/activity channels, metadata loopback startup, network-only lifecycle | Keep as orchestration; avoid embedding per-entry proxy policy decisions | +| `openshell-supervisor-network::run` | Networking startup and handles | Become the stable runtime API for embedded and future standalone modes | +| `openshell-supervisor-network::proxy` | CONNECT, forward HTTP, local route dispatch, destination validation, denial rendering | Split into adapters, authorization, destination, relay selection, and adapter response rendering | +| `openshell-supervisor-network::opa` | Policy engine and Rego queries | Return deterministic `EgressDecision` data instead of separate policy and endpoint lookups | +| `openshell-supervisor-network::l7` | REST, GraphQL, JSON-RPC, MCP, WebSocket, inference helpers, TLS, token grants | Keep as protocol/relay implementation behind shared relay boundaries | +| `openshell-supervisor-network::policy_local` | `policy.local` state and routes | Model as a local adapter with explicit limits and proposal/wait behavior | +| `openshell-supervisor-middleware` | Middleware registry, built-ins, service contract, and chain execution | Treat as a relay hook dependency selected by `EgressDecision`, not as adapter-specific policy logic | +| `openshell-supervisor-process::netns` | nftables bypass rules and namespace helpers | Remain owner of bypass enforcement; coordinate future capture rules with network proxy mappings | +| `openshell-supervisor-process::bypass_monitor` | nftables LOG parsing and OCSF bypass telemetry | Remain telemetry producer for bypass violations | +| `openshell-core::secrets` and provider credential state | Static placeholder sources and dynamic credential metadata | Feed credential injection plans; do not leak secrets into decision logs | + +## Policy DNS And Resolved TCP State + +Policy DNS is query-driven rather than a static `/etc/hosts` snapshot. + +1. Policy load registers eligible native TCP endpoint names. +2. Userland performs a DNS lookup. +3. Policy DNS checks whether the normalized name matches an endpoint whose + transport and protocol contract enables native TCP through policy DNS in the + current policy generation. +4. An ineligible name receives a local policy-denial DNS response without an + upstream query. +5. Policy DNS resolves an eligible name through trusted upstream DNS and + filters every answer through endpoint metadata and SSRF controls. +6. The adapter allocates a supervisor-owned synthetic IP and creates an active + mapping containing the synthetic IP, normalized name, endpoint identifier, + allowed ports, validated real addresses, policy generation, distinct DNS + mapping generation, opaque mapping ID, and expiration. +7. The adapter publishes the mapping and capture state atomically before + returning the synthetic IP to userland with a bounded TTL. +8. Userland later calls `connect(synthetic_ip:port)`. +9. Transparent TCP recovers the synthetic original destination and requires an + unexpired exact mapping whose allowed ports contain the requested port. +10. Normal egress authorization and relay selection run against a policy + generation consistent with the mapping contract. +11. The connector dials only a real address pinned in that mapping. It does not + re-resolve the name independently at connect time. + +The resolved endpoint store is active state produced by policy-eligible lookups +and consumed by transparent TCP connects. Policy generation and DNS mapping +generation are separate values: a DNS refresh can replace mappings without a +policy reload, while a policy reload can invalidate mappings whose endpoint +contract is no longer current. A captured connection with no mapping, a stale +mapping, or a mismatched endpoint/port fails closed. An unrelated bare-IP +connection cannot inherit a policy-DNS authorization merely because it targets +a real IP present in the mapping store. Synthetic IPs are correlation handles, +not upstream destinations, and must never be routed directly or reassigned +while a stale answer could still refer to the prior mapping. + +The mapping is sandbox-scoped rather than process-scoped. Process identity is +looked up and evaluated independently when the captured TCP connection is +authorized. It is not used to join DNS and TCP because name resolution may be +cached, delegated to a resolver helper, or consumed by a different process, and +multiple names resolved by one process may share a real address and port. + +## nftables Boundary + +Current main uses nftables, not iptables, for sandbox network bypass +enforcement. The installed `inet` table accepts traffic to the sandbox proxy, +loopback, and established/related flows, then rejects and optionally logs other +TCP/UDP traffic. The bypass monitor reads those log lines and emits OCSF +network and detection events. + +Transparent TCP capture builds on this same nftables substrate in a later +feature phase: + +- capture rules run before the generic bypass reject rules; +- capture rules are scoped to active synthetic-IP and allowed-port mappings; +- mapping and capture-rule updates are atomic from the adapter's perspective; +- direct external DNS remains blocked; policy DNS is the sandbox resolver, and + DNS-over-HTTPS remains ordinary policy-controlled HTTPS egress; +- reject/log rules remain the fallback for unmatched TCP/UDP egress; +- VM or Podman driver nftables rules are infrastructure NAT/isolation and are + not the proxy policy enforcement point. + +The initial CONNECT/forward refactor does not change the installed table. This +section defines the consumer contract that the shared adapter and decision +boundaries must support when transparent capture lands. + +## Endpoint Selection And OPA + +Today the matched policy name, L7 candidates, first TLS/`allowed_ips` endpoint, +and exact-declared-host signal are selected through independent rules. OPA/Rego +should return policy and endpoint metadata through one deterministic +authorization result. It should not let those fields describe different +matches. + +Two acceptable approaches: + +- Reject overlapping endpoint metadata at load or merge time. +- Define a single deterministic precedence key and use it for both policy name + and endpoint metadata. + +Endpoint metadata query failures should fail closed when metadata is required +for the selected endpoint. They should not silently downgrade to L4 behavior. +The top-level decision generation must also match every policy-derived field; +reload during materialization yields a stale decision instead of mixing +generations. + +This semantic cutover is separate from introducing the Rust types. The new +query first runs in shadow mode beside the legacy queries, records audit-safe +mismatches through internal telemetry, and preserves legacy enforcement. After +the precedence rule is accepted and mismatch cases are understood, a dedicated +change switches the authoritative result and retains the legacy evaluator long +enough for immediate rollback. + +Provider-derived policies use a reserved rule-name namespace. The gateway and +sandbox sync should prevent user-authored `_provider_*` rules, and +`policy.local` proposal surfaces should not expose provider-derived rules as +editable user policy. `EgressDecision` should still identify provider-derived +matches for logging and debugging. + +## Credential Injection Boundary + +Credential injection belongs in the HTTP/WebSocket relay after policy allow and +supervisor middleware, and before upstream write. + +1. Authorization selects the endpoint and computes a credential injection plan. +2. Supervisor middleware runs on the admitted request before credentials are + visible. +3. If middleware replaces the body, the relay re-parses body-dependent + protocol inputs and re-evaluates request policy. +4. The HTTP relay resolves credentials only when it still has an allowed + request under the endpoint's enforcement mode. +5. Static placeholder values are resolved and redacted from logs. +6. Endpoint-bound token grants obtain or reuse a dynamic access token. +7. The final upstream request or WebSocket frame is rewritten immediately + before write. + +Both L4-only HTTP and HTTP-inspected paths can inject credentials. The +difference is whether REST, GraphQL, or WebSocket policy is evaluated before +the rewrite. + +Credential rewrite slots should be explicit: + +- request target, query values, and headers for HTTP-family traffic; +- REST request bodies only when `request_body_credential_rewrite` is enabled; +- client-to-server WebSocket text frames only when + `websocket_credential_rewrite` is enabled; +- GraphQL-over-WebSocket connection/control messages when they are carried in + text frames and the endpoint enables the WebSocket rewrite path; +- token grant headers for endpoint-bound provider credentials. + +Request-body rewrite is REST-only. It should buffer bounded UTF-8 textual +bodies, including JSON, form-url-encoded, and `text/*`, recompute +`Content-Length`, preserve unsupported bodies that contain no reserved +credential markers, and fail closed when a reserved placeholder cannot be +resolved safely. Binary WebSocket frames are not rewritten. + +Token grants are dynamic credential injection. They use provider metadata to +request a SPIFFE JWT-SVID, exchange it for an OAuth2 access token, cache the +token, and inject either an `Authorization: Bearer` header or a configured +custom header. Token grant failures should return a local relay error and must +not forward the request upstream. + +Middleware-transformed content should be treated as untrusted input from a +credential perspective. External middleware must not receive OpenShell-managed +credentials, and it should not be able to synthesize new reserved credential +placeholders that OpenShell later resolves into secrets. Unless a future hook +is explicitly built-in-only and credential-capable, the relay should fail +closed or strip newly introduced reserved placeholders before static +placeholder rewrite and token grant injection. + +## Supervisor Middleware Boundary + +Supervisor middleware is a typed relay hook, not a replacement for protocol +framing. The relay or protocol processor must first parse enough structure to +construct the operation-specific middleware input. + +For v1, the operation is `HTTP_REQUEST / PRE_CREDENTIALS`: + +1. Network policy, destination validation, and request policy admit the + request. +2. The HTTP relay selects the middleware chain from the request processing + plan. +3. The relay buffers the request body within the smallest selected stage limit. +4. The chain evaluates in deterministic order. +5. A deny short-circuits before credential injection or upstream write. +6. An allow can replace the request body, add approved headers, emit findings, + and pass metadata forward. +7. When the body changes, the relay re-parses and re-evaluates body-dependent + request policy inputs. +8. The transformed request enters credential injection and upstream write only + after that re-evaluation admits it under the endpoint's enforcement mode. + +The re-evaluation uses the original request method, path, and query because v1 +middleware cannot mutate them. It re-derives the GraphQL operation, JSON-RPC +method, and MCP method or tool name from the transformed body. A policy mismatch +preserves the endpoint's audit or enforce behavior. A malformed or +unclassifiable transformed protocol body fails closed in both modes because the +relay can no longer prove which operation it would forward. + +Middleware selection is independent from the matched endpoint policy. It is a +request processing plan selected by admitted destination host, order, and +binding metadata. The decision boundary should materialize it with the same +policy generation used for endpoint selection so a long-lived tunnel cannot mix +old endpoint policy with a new middleware registry. + +V1 middleware can inspect WebSocket upgrade requests because those are HTTP +requests. It does not inspect post-upgrade WebSocket frames. A future frame +hook should be a separate operation such as `WEBSOCKET_MESSAGE / +BEFORE_FORWARD` owned by the WebSocket relay. + +## Protocol Processor Boundary + +Protocol processors operate on streams owned by the relay. + +- HTTP parsing converts bytes into request metadata, evaluates request policy, + runs the `HTTP_REQUEST / PRE_CREDENTIALS` middleware hook when configured, + and loops for keep-alive or pipelined requests. +- JSON-RPC and MCP processing are HTTP L7 processors: they parse bounded + JSON-RPC-over-HTTP request bodies after HTTP parsing and before upstream + forwarding. Generic JSON-RPC policy matches methods; MCP policy can also + match `tools/call` tool names. +- WebSocket parsing starts only after an allowed HTTP upgrade. It validates the + handshake/frame stream and owns client-to-server text-frame inspection when + credential rewrite, transport message policy, GraphQL-over-WebSocket policy, + or compression handling is configured. +- Native TCP protocol processors read client and upstream streams as needed and + own their message loop. +- A protocol processor can deny before dialing, dial for a server handshake, or + keep evaluating commands or queries throughout the session. +- A protocol processor may be in-tree, middleware-backed, or a hybrid where + in-tree framing exposes typed middleware operations for content evaluation. + +HTTP and WebSocket relays receive the generation-pinned request evaluator +because request policy must continue throughout long-lived sessions. No +processor rematerializes endpoint, TLS, allowed-IP, credential, or middleware +selection. This avoids a separate dial-strategy enum: each processor knows +which protocol milestone is sufficient to call the validated connector. + +## Local Service Adapter Boundary + +Local services are network surfaces but not normal external egress: + +- `inference.local` terminates local client traffic, validates known inference + routes, strips caller auth, injects provider routing/auth, and applies + streaming or buffered limits based on route type. +- `policy.local` serves policy snapshots, denial summaries, proposal + submission, and proposal wait. It should never expose secrets or provider + rules as editable policy. +- Metadata loopback serves provider metadata credentials for SDKs that bypass + HTTP proxy variables. It should use the same provider credential state and + redaction discipline as other credential paths. + +These adapters may call gateway APIs or local credential helpers, but they +should not bypass policy and credential invariants that apply to external +egress. + +Issue [#1633](https://github.com/NVIDIA/OpenShell/issues/1633) is a prospective +consumer of these boundaries, not a feature defined by this RFC. A +policy-declared host-local endpoint should use an explicit local-routing adapter +or destination mode; it must not become a general loopback exemption in the +external destination validator. Its feature design still needs to choose the +policy surface (reserved hostname versus endpoint flag), define authorization +before the supervisor connects to host loopback, and specify driver/runtime +capabilities. That work can reuse `EgressIntent`, adapter-specific responses, +and the unopened connector boundary without changing this RFC's compatibility +milestone. + +## Timeout And Resource Ownership + +| Owner | Resource | +|-------|----------| +| Adapter | Client-side parse timeout and adapter-specific deny response | +| Authorization | OPA deadline and policy evaluation telemetry | +| Destination validator | DNS timeout, allowed IP checks, SSRF checks, control-plane port checks | +| TLS terminator | Client TLS handshake timeout and certificate selection | +| HTTP relay | Per-request read/write deadlines, body caps, request-body rewrite caps, upstream reuse | +| WebSocket relay | Upgrade validation, frame limits, text-frame rewrite, compression limits, message policy | +| TCP relay | Byte-copy idle timeout and half-close handling | +| Protocol processor | Protocol message timeouts, middleware hook timeouts, and processor-specific limits | +| Local service adapter | Local route body limits, response caps, gateway call timeout | +| Token grant resolver | SPIFFE Workload API timeout, token endpoint timeout, cache TTL | +| Middleware runner | Service timeout, body cap, failure policy, registry generation | + +Timeouts should be recorded in telemetry at the owner boundary that can explain +the failure. From d2c44b0e5393e3746eae5783aa99ac31be08daae Mon Sep 17 00:00:00 2001 From: Artem Lytvyn Date: Thu, 6 Aug 2026 00:16:59 +0100 Subject: [PATCH 4/8] fix(supervisor-middleware): configure HTTP/2 keepalive on middleware gRPC channel (#2608) Signed-off-by: Artem Lytvyn --- crates/openshell-supervisor-middleware/src/remote.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/openshell-supervisor-middleware/src/remote.rs b/crates/openshell-supervisor-middleware/src/remote.rs index 378dac3ec..30ea5a74b 100644 --- a/crates/openshell-supervisor-middleware/src/remote.rs +++ b/crates/openshell-supervisor-middleware/src/remote.rs @@ -16,6 +16,8 @@ use tonic::{Request, Response, Status}; use crate::MIDDLEWARE_GRPC_MESSAGE_BYTES; const CONNECT_TIMEOUT: Duration = Duration::from_secs(5); +const HTTP2_KEEP_ALIVE_INTERVAL: Duration = Duration::from_secs(10); +const HTTP2_KEEP_ALIVE_TIMEOUT: Duration = Duration::from_secs(20); #[derive(Clone)] pub struct RemoteMiddlewareService { @@ -30,7 +32,12 @@ impl RemoteMiddlewareService { format!( "middleware registration '{registration_name}' has an invalid grpc_endpoint" ) - })?; + })? + .http2_keep_alive_interval(HTTP2_KEEP_ALIVE_INTERVAL) + .keep_alive_while_idle(true) + .keep_alive_timeout(HTTP2_KEEP_ALIVE_TIMEOUT) + .http2_adaptive_window(true); + if grpc_endpoint.starts_with("https://") { endpoint = endpoint .tls_config(ClientTlsConfig::new().with_enabled_roots()) @@ -39,6 +46,7 @@ impl RemoteMiddlewareService { format!("middleware registration '{registration_name}' could not configure TLS") })?; } + let channel = endpoint .connect_timeout(CONNECT_TIMEOUT) .connect() @@ -49,6 +57,7 @@ impl RemoteMiddlewareService { "middleware registration '{registration_name}' could not connect to {grpc_endpoint}" ) })?; + Ok(Self { client: SupervisorMiddlewareClient::new(channel) .max_decoding_message_size(MIDDLEWARE_GRPC_MESSAGE_BYTES) From 0c7e59a95355cabc15ccaddb86fcbe6a1d30eaaa Mon Sep 17 00:00:00 2001 From: alangou Date: Thu, 6 Aug 2026 11:39:43 +0200 Subject: [PATCH 5/8] fix(deps): bump russh, jsonwebtoken, tar and npm lint deps (#2617) Signed-off-by: Adrien Langou --- Cargo.lock | 145 +++++----- Cargo.toml | 2 +- crates/openshell-server/Cargo.toml | 4 +- crates/openshell-server/src/auth/oidc.rs | 252 ++++++++++++++++++ .../openshell-supervisor-process/Cargo.toml | 2 +- .../openshell-supervisor-process/src/ssh.rs | 203 +++++++++++++- scripts/lint-mermaid/package-lock.json | 211 +++------------ 7 files changed, 575 insertions(+), 244 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88c7dc0b7..acf5fff2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,7 +127,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -138,7 +138,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1356,15 +1356,16 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "5.0.0-rc.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f359e08ca85e7bd759e1fd933ff2bccd81864c60a8fba0e259c7f822b0924bf" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" dependencies = [ "cfg-if", "cpufeatures 0.3.0", "curve25519-dalek-derive", "digest 0.11.2", "fiat-crypto", + "rand_core 0.10.1", "rustc_version", "subtle", "zeroize", @@ -1615,9 +1616,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "ecdsa" -version = "0.17.0-rc.18" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54fb064faabbee66e1fc8e5c5a9458d4269dc2d8b638fe86a425adb2510d1a96" +checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" dependencies = [ "der 0.8.0", "digest 0.11.2", @@ -1640,9 +1641,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "3.0.0-rc.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011170fe4f04665565b4110afef66774fe9ffff278f3eb5b81cc73d26e27d60" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" dependencies = [ "curve25519-dalek", "ed25519", @@ -1665,9 +1666,9 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.14.0-rc.33" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102d3643d30dd8b559613c5cced68317199597fffb278cdc88daa2ef7fafc935" +checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65" dependencies = [ "base16ct", "crypto-bigint", @@ -1677,7 +1678,6 @@ dependencies = [ "group", "hkdf 0.13.0", "hybrid-array", - "once_cell", "pem-rfc7468 1.0.0", "pkcs8 0.11.0", "rand_core 0.10.1", @@ -1737,7 +1737,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2869,33 +2869,21 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "jsonwebtoken" -version = "9.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" -dependencies = [ - "base64 0.22.1", - "js-sys", - "pem", - "ring", - "serde", - "serde_json", - "simple_asn1", -] - [[package]] name = "jsonwebtoken" version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ + "aws-lc-rs", "base64 0.22.1", "getrandom 0.2.17", "js-sys", + "pem", "serde", "serde_json", "signature 2.2.0", + "simple_asn1", ] [[package]] @@ -3371,7 +3359,7 @@ dependencies = [ "module-lattice", "pkcs8 0.11.0", "rand_core 0.10.1", - "sha3", + "sha3 0.11.0", ] [[package]] @@ -3467,7 +3455,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3588,7 +3576,7 @@ dependencies = [ "futures-util", "http 1.4.0", "http-auth", - "jsonwebtoken 10.3.0", + "jsonwebtoken", "lazy_static", "oci-spec", "olpc-cjson", @@ -4108,7 +4096,7 @@ dependencies = [ "hyper-rustls 0.27.9", "hyper-util", "ipnet", - "jsonwebtoken 9.3.1", + "jsonwebtoken", "k8s-openapi", "kube", "metrics", @@ -4144,6 +4132,7 @@ dependencies = [ "rcgen", "reqwest 0.12.28", "ring", + "rsa 0.9.10", "russh", "rustix 1.1.4", "rustls 0.23.38", @@ -4427,9 +4416,9 @@ checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" [[package]] name = "p256" -version = "0.14.0-rc.10" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41adc63effe99d48837a8cc0e6d7a77e32ae6a07f6000df466178dbc2193093e" +checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a" dependencies = [ "ecdsa", "elliptic-curve", @@ -4440,9 +4429,9 @@ dependencies = [ [[package]] name = "p384" -version = "0.14.0-rc.10" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd5333afa5ae0347f39e6a0f2c9c155da431583fd71fe5555bd0521b4ccaf02" +checksum = "d17b851e6b3e378ab4ecb07fa2ed23f4d15f075735f8fec9fa1e7bdce5f8301f" dependencies = [ "ecdsa", "elliptic-curve", @@ -4454,9 +4443,9 @@ dependencies = [ [[package]] name = "p521" -version = "0.14.0-rc.10" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3a5297f53dc16d35909060ba3032cff7867e8809f01e273ff325579d5f0ceae" +checksum = "4ad64cc32c2dc466317c12ee5853e61f159f9eab1fe7efade0395dc2e7b43449" dependencies = [ "base16ct", "ecdsa", @@ -4847,11 +4836,15 @@ dependencies = [ [[package]] name = "primeorder" -version = "0.14.0-rc.10" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d2793f22b9b6fd11ef3ac1d59bf003c2573593e4968702341605c2748fd90bf" +checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06" dependencies = [ "elliptic-curve", + "once_cell", + "primefield", + "serdect", + "wnaf", ] [[package]] @@ -5354,12 +5347,12 @@ dependencies = [ [[package]] name = "rfc6979" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5236ce872cac07e0fb3969b0cbf468c7d2f37d432f1b627dcb7b8d34563fb0c3" +checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b" dependencies = [ + "crypto-bigint", "hmac 0.13.0", - "subtle", ] [[package]] @@ -5429,9 +5422,9 @@ dependencies = [ [[package]] name = "russh" -version = "0.61.2" +version = "0.62.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf893f64684e58da8a68d56a5e84d1cf0440226274c515770fe267707a7d0b0" +checksum = "da7c230e0ed9cbeb92fbad6c8848985d6df2a1464c0dc247a021abd666e9005e" dependencies = [ "aes", "aws-lc-rs", @@ -5486,7 +5479,7 @@ dependencies = [ "sec1", "sha1 0.11.0", "sha2 0.11.0", - "sha3", + "sha3 0.12.0", "signature 3.0.0", "spki 0.8.0", "ssh-encoding", @@ -5501,9 +5494,9 @@ dependencies = [ [[package]] name = "russh-cryptovec" -version = "0.61.0" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443f6bbcfacb34a1aab2b12b99bf08e0c63abdc5a0db261901365df9d57fff51" +checksum = "3aec6cb630dbe85d72ffd7bcd95f07e1bd69f9f270ee8adfa1afe443a6331438" dependencies = [ "log", "nix 0.31.3", @@ -5582,7 +5575,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5662,7 +5655,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6046,6 +6039,17 @@ dependencies = [ "keccak", ] +[[package]] +name = "sha3" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9bad02c26382724b2d2692c6f179285e4b54eeecd7968f52a50059c3c11759" +dependencies = [ + "digest 0.11.2", + "keccak", + "sponge-cursor", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -6181,7 +6185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -6241,6 +6245,12 @@ dependencies = [ "der 0.8.0", ] +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" + [[package]] name = "sqlx" version = "0.8.6" @@ -6431,17 +6441,15 @@ dependencies = [ [[package]] name = "ssh-cipher" -version = "0.3.0-rc.9" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10db6f219196a8528f9ec904d9d45cdad692d65b0e57e72be4dedd1c5fddce36" +checksum = "d801accda99469cde6d73da741422610fdf6508a72d9a69d1b55cb241c720597" dependencies = [ "aead", "aes", "aes-gcm", - "cbc", "chacha20", "cipher", - "ctr", "ctutils", "des", "poly1305", @@ -6451,9 +6459,9 @@ dependencies = [ [[package]] name = "ssh-encoding" -version = "0.3.0-rc.9" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abf34aa716da5d5b4c496936d042ea282ab392092cd68a72ef6a8863ff8c96a" +checksum = "7b54d0ed0498daf3f78d82e00e28c8eec9d75a067c4cfbcc7a0f7d0f4077749e" dependencies = [ "base64ct", "bytes", @@ -6466,9 +6474,9 @@ dependencies = [ [[package]] name = "ssh-key" -version = "0.7.0-rc.10" +version = "0.7.0-rc.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45735ce3dea95690e4a9e414c4cfde7f79835063c3dcd35881df85a84118e74b" +checksum = "f9a32fae177b74a22aa9c5b01bf7e68b33545be32d9e381e248058d2adc15ce3" dependencies = [ "argon2", "bcrypt-pbkdf", @@ -6646,9 +6654,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.45" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" dependencies = [ "filetime", "libc", @@ -6674,7 +6682,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6710,7 +6718,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -7707,7 +7715,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -8249,6 +8257,17 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wnaf" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab12e7090f27e2ffd9322651492942d50c2926094af30601e1964337db39daf1" +dependencies = [ + "ff", + "group", + "hybrid-array", +] + [[package]] name = "writeable" version = "0.6.3" diff --git a/Cargo.toml b/Cargo.toml index 9d801570e..26c1f72f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,7 +102,7 @@ base64 = "0.22" # Crypto / Auth sha2 = "0.10" rand = "0.9" -jsonwebtoken = "9" +jsonwebtoken = { version = "10", features = ["aws_lc_rs"] } getrandom = "0.3" ring = "0.17" spiffe = { version = "0.15", default-features = false, features = ["workload-api-jwt", "tracing"] } diff --git a/crates/openshell-server/Cargo.toml b/crates/openshell-server/Cargo.toml index 7182d0f70..e158edda4 100644 --- a/crates/openshell-server/Cargo.toml +++ b/crates/openshell-server/Cargo.toml @@ -106,7 +106,7 @@ async-trait = "0.1" url = { workspace = true } glob = { workspace = true } hex = "0.4" -russh = "0.61" +russh = "0.62" rand = { workspace = true } petname = "2" ipnet = "2" @@ -128,6 +128,8 @@ test-support = [] [dev-dependencies] hyper-rustls = { version = "0.27", default-features = false, features = ["native-tokio", "http1", "tls12", "logging", "ring"] } rcgen = { version = "0.13", features = ["crypto", "pem"] } +rsa = { version = "0.9", features = ["pem"] } +base64 = { workspace = true } tokio-tungstenite = { workspace = true } futures-util = "0.3" wiremock = "0.6" diff --git a/crates/openshell-server/src/auth/oidc.rs b/crates/openshell-server/src/auth/oidc.rs index cbe83ff06..fd599b501 100644 --- a/crates/openshell-server/src/auth/oidc.rs +++ b/crates/openshell-server/src/auth/oidc.rs @@ -510,4 +510,256 @@ mod tests { let scopes = claims.extract_scopes("scope"); assert!(scopes.is_empty()); } + + // ----------------------------------------------------------------------- + // RS256 verification through the real JWKS path + // + // The tests above only cover claim extraction from an already-trusted + // payload. These sign real RS256 tokens and push them through + // `JwksCache::new` + `validate_token`, so the JWKS `n`/`e` decoding and the + // RSA signature check are exercised against whichever crypto backend + // `jsonwebtoken` is built with — a backend swap is otherwise invisible to + // the test suite. + // ----------------------------------------------------------------------- + + const TEST_KID: &str = "test-signing-key"; + const TEST_AUDIENCE: &str = "openshell-cli"; + + /// One RSA key per test binary. Key generation dominates the runtime of + /// these tests and the key carries no meaning beyond being valid. + static TEST_RSA_KEY: std::sync::LazyLock = + std::sync::LazyLock::new(TestRsaKey::generate); + + struct TestRsaKey { + private_pem: String, + modulus_b64: String, + exponent_b64: String, + } + + impl TestRsaKey { + fn generate() -> Self { + use base64::Engine as _; + use rsa::pkcs1::EncodeRsaPrivateKey as _; + use rsa::traits::PublicKeyParts as _; + + let private = rsa::RsaPrivateKey::new(&mut rsa::rand_core::OsRng, 2048) + .expect("generate RSA test key"); + let b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD; + Self { + private_pem: private + .to_pkcs1_pem(rsa::pkcs1::LineEnding::LF) + .expect("encode RSA private key as PEM") + .to_string(), + modulus_b64: b64.encode(private.n().to_bytes_be()), + exponent_b64: b64.encode(private.e().to_bytes_be()), + } + } + } + + fn now_secs() -> i64 { + i64::try_from( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("system clock is after the unix epoch") + .as_secs(), + ) + .expect("current time fits in i64") + } + + /// Sign `claims` with the test key, tagging the header with `kid`. + fn mint_rs256(claims: &serde_json::Value, kid: &str) -> String { + let mut header = jsonwebtoken::Header::new(Algorithm::RS256); + header.kid = Some(kid.to_owned()); + let key = jsonwebtoken::EncodingKey::from_rsa_pem(TEST_RSA_KEY.private_pem.as_bytes()) + .expect("load RSA signing key"); + jsonwebtoken::encode(&header, claims, &key).expect("sign RS256 token") + } + + fn claims_for(issuer: &str, audience: &str, exp: i64) -> serde_json::Value { + serde_json::json!({ + "sub": "user-42", + "preferred_username": "ada", + "iss": issuer, + "aud": audience, + "exp": exp, + "scope": "openid profile sandbox:write", + "realm_access": { "roles": ["openshell-user"] }, + }) + } + + /// Serve an OIDC discovery document and a JWKS carrying the test key, then + /// build a cache against them the same way production does. + async fn cache_with_mock_issuer(server: &wiremock::MockServer) -> JwksCache { + use wiremock::matchers::{method, path}; + use wiremock::{Mock, ResponseTemplate}; + + let issuer = server.uri(); + Mock::given(method("GET")) + .and(path("/.well-known/openid-configuration")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "issuer": issuer, + "jwks_uri": format!("{issuer}/jwks"), + }))) + .mount(server) + .await; + Mock::given(method("GET")) + .and(path("/jwks")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "keys": [{ + "kid": TEST_KID, + "kty": "RSA", + "n": TEST_RSA_KEY.modulus_b64, + "e": TEST_RSA_KEY.exponent_b64, + }], + }))) + .mount(server) + .await; + + JwksCache::new(&OidcConfig { + issuer, + audience: TEST_AUDIENCE.to_owned(), + jwks_ttl_secs: 3600, + roles_claim: "realm_access.roles".to_owned(), + admin_role: "openshell-admin".to_owned(), + user_role: "openshell-user".to_owned(), + scopes_claim: "scope".to_owned(), + }) + .await + .expect("cache should build from the mock issuer") + } + + #[tokio::test] + async fn rs256_token_signed_by_jwks_key_is_accepted() { + let server = wiremock::MockServer::start().await; + let cache = cache_with_mock_issuer(&server).await; + + let token = mint_rs256( + &claims_for(&server.uri(), TEST_AUDIENCE, now_secs() + 3600), + TEST_KID, + ); + let identity = cache + .validate_token(&token) + .await + .expect("a correctly signed token must be accepted"); + + assert_eq!(identity.subject, "user-42"); + assert_eq!(identity.display_name.as_deref(), Some("ada")); + assert_eq!(identity.roles, vec!["openshell-user".to_owned()]); + assert_eq!(identity.scopes, vec!["sandbox:write".to_owned()]); + assert_eq!(identity.provider, IdentityProvider::Oidc); + } + + #[tokio::test] + async fn rs256_token_with_tampered_payload_is_rejected() { + let server = wiremock::MockServer::start().await; + let cache = cache_with_mock_issuer(&server).await; + + let exp = now_secs() + 3600; + let token = mint_rs256(&claims_for(&server.uri(), TEST_AUDIENCE, exp), TEST_KID); + + // Keep the header and signature but swap in a payload that escalates + // the subject: only the RSA check stands between this and an identity. + let segments: Vec<&str> = token.split('.').collect(); + assert_eq!(segments.len(), 3, "a JWT has three segments"); + let mut forged_claims = claims_for(&server.uri(), TEST_AUDIENCE, exp); + forged_claims["sub"] = serde_json::json!("root"); + let forged_payload = { + use base64::Engine as _; + base64::engine::general_purpose::URL_SAFE_NO_PAD + .encode(serde_json::to_vec(&forged_claims).expect("serialize forged claims")) + }; + let forged = format!("{}.{forged_payload}.{}", segments[0], segments[2]); + + cache + .validate_token(&forged) + .await + .expect_err("a swapped payload must fail the signature check"); + } + + #[tokio::test] + async fn rs256_token_signed_by_unrelated_key_is_rejected() { + let server = wiremock::MockServer::start().await; + let cache = cache_with_mock_issuer(&server).await; + + let other = TestRsaKey::generate(); + let mut header = jsonwebtoken::Header::new(Algorithm::RS256); + header.kid = Some(TEST_KID.to_owned()); + let token = jsonwebtoken::encode( + &header, + &claims_for(&server.uri(), TEST_AUDIENCE, now_secs() + 3600), + &jsonwebtoken::EncodingKey::from_rsa_pem(other.private_pem.as_bytes()) + .expect("load unrelated signing key"), + ) + .expect("sign with unrelated key"); + + cache + .validate_token(&token) + .await + .expect_err("a token signed by a key outside the JWKS must be rejected"); + } + + #[tokio::test] + async fn rs256_expired_token_is_rejected() { + let server = wiremock::MockServer::start().await; + let cache = cache_with_mock_issuer(&server).await; + + // Beyond the 60s default leeway. + let token = mint_rs256( + &claims_for(&server.uri(), TEST_AUDIENCE, now_secs() - 3600), + TEST_KID, + ); + + cache + .validate_token(&token) + .await + .expect_err("an expired token must be rejected"); + } + + #[tokio::test] + async fn rs256_token_from_other_issuer_is_rejected() { + let server = wiremock::MockServer::start().await; + let cache = cache_with_mock_issuer(&server).await; + + let token = mint_rs256( + &claims_for("https://evil.example.com", TEST_AUDIENCE, now_secs() + 3600), + TEST_KID, + ); + + cache + .validate_token(&token) + .await + .expect_err("a token from another issuer must be rejected"); + } + + #[tokio::test] + async fn rs256_token_for_other_audience_is_rejected() { + let server = wiremock::MockServer::start().await; + let cache = cache_with_mock_issuer(&server).await; + + let token = mint_rs256( + &claims_for(&server.uri(), "some-other-client", now_secs() + 3600), + TEST_KID, + ); + + cache + .validate_token(&token) + .await + .expect_err("a token minted for another audience must be rejected"); + } + + #[tokio::test] + async fn rs256_token_with_unknown_kid_is_rejected() { + let server = wiremock::MockServer::start().await; + let cache = cache_with_mock_issuer(&server).await; + + let token = mint_rs256( + &claims_for(&server.uri(), TEST_AUDIENCE, now_secs() + 3600), + "rotated-away-key", + ); + + cache + .validate_token(&token) + .await + .expect_err("a token naming a kid absent from the JWKS must be rejected"); + } } diff --git a/crates/openshell-supervisor-process/Cargo.toml b/crates/openshell-supervisor-process/Cargo.toml index 575923d4c..7b9188758 100644 --- a/crates/openshell-supervisor-process/Cargo.toml +++ b/crates/openshell-supervisor-process/Cargo.toml @@ -21,7 +21,7 @@ hex = "0.4" miette = { workspace = true } nix = { workspace = true } rand = "0.10" -russh = "0.61" +russh = "0.62" serde_json = { workspace = true } sha2 = { workspace = true } tokio = { workspace = true } diff --git a/crates/openshell-supervisor-process/src/ssh.rs b/crates/openshell-supervisor-process/src/ssh.rs index be1b67953..07302da95 100644 --- a/crates/openshell-supervisor-process/src/ssh.rs +++ b/crates/openshell-supervisor-process/src/ssh.rs @@ -20,9 +20,9 @@ use openshell_core::provider_credentials::ProviderCredentialState; use openshell_ocsf::{ ActionId, ActivityId, DispositionId, SeverityId, SshActivityBuilder, StatusId, ocsf_emit, }; -use russh::ChannelId; use russh::keys::{Algorithm, PrivateKey}; -use russh::server::{Auth, Handle, Session}; +use russh::server::{Auth, ChannelOpenHandle, Handle, Session}; +use russh::{ChannelId, ChannelOpenFailure}; use std::collections::HashMap; use std::io::{Read, Write}; use std::os::fd::{AsRawFd, RawFd}; @@ -297,10 +297,12 @@ impl russh::server::Handler for SshHandler { async fn channel_open_session( &mut self, channel: russh::Channel, + reply: ChannelOpenHandle, _session: &mut Session, - ) -> Result { + ) -> Result<(), Self::Error> { self.channels.insert(channel.id(), ChannelState::default()); - Ok(true) + reply.accept().await; + Ok(()) } /// Clean up per-channel state when the channel is closed. @@ -324,8 +326,9 @@ impl russh::server::Handler for SshHandler { port_to_connect: u32, _originator_address: &str, _originator_port: u32, + reply: ChannelOpenHandle, _session: &mut Session, - ) -> Result { + ) -> Result<(), Self::Error> { // Validate port range before truncating u32 -> u16. The SSH protocol // uses u32 for ports, but valid TCP ports are 0-65535. Without this // check, port 65537 truncates to port 1 (privileged). @@ -339,7 +342,10 @@ impl russh::server::Handler for SshHandler { "direct-tcpip rejected: port {port_to_connect} exceeds valid TCP range for host {host_to_connect}" )) .build()); - return Ok(false); + reply + .reject(ChannelOpenFailure::AdministrativelyProhibited) + .await; + return Ok(()); } // Only allow forwarding to loopback destinations to prevent the @@ -354,7 +360,10 @@ impl russh::server::Handler for SshHandler { "direct-tcpip rejected: non-loopback destination {host_to_connect}:{port_to_connect}" )) .build()); - return Ok(false); + reply + .reject(ChannelOpenFailure::AdministrativelyProhibited) + .await; + return Ok(()); } let host = host_to_connect.to_string(); @@ -363,6 +372,10 @@ impl russh::server::Handler for SshHandler { let port = u16::try_from(port_to_connect).unwrap_or(u16::MAX); let netns_fd = self.netns_fd; + // Confirm the channel before spawning: the task below writes to it, and + // the peer must see the open-confirmation first. + reply.accept().await; + tokio::spawn(async move { let addr = format!("{host}:{port}"); let tcp = match connect_in_netns(&addr, netns_fd).await { @@ -387,7 +400,7 @@ impl russh::server::Handler for SshHandler { let _ = tokio::io::copy_bidirectional(&mut channel_stream, &mut tcp_stream).await; }); - Ok(true) + Ok(()) } async fn pty_request( @@ -1951,4 +1964,178 @@ mod tests { "resolved-identity-ok" ); } + + // ----------------------------------------------------------------------- + // direct-tcpip authorization wiring (SEC-007) + // + // The `loopback_host_*` tests above cover the predicate in isolation. + // These drive the real `russh::server::Handler` over an in-memory duplex + // so the deny path itself is covered: channel-open authorization travels + // through a reply handle rather than the handler's return value, so a + // handler that never rejects anything still type-checks and still passes + // every predicate test. + // ----------------------------------------------------------------------- + + struct AcceptAnyServerKey; + + impl russh::client::Handler for AcceptAnyServerKey { + type Error = russh::Error; + + async fn check_server_key( + &mut self, + _server_public_key: &russh::keys::PublicKey, + ) -> Result { + Ok(true) + } + } + + fn forwarding_test_policy() -> SandboxPolicy { + use openshell_core::policy::{ + FilesystemPolicy, LandlockPolicy, NetworkPolicy, ProcessPolicy, + }; + + SandboxPolicy { + version: 0, + filesystem: FilesystemPolicy::default(), + network: NetworkPolicy::default(), + landlock: LandlockPolicy::default(), + process: ProcessPolicy { + run_as_user: None, + run_as_group: None, + }, + } + } + + /// Serve `SshHandler` on one end of an in-memory duplex and return an + /// authenticated client handle for the other end. + /// + /// The handler gets `netns_fd: None` so `connect_in_netns` performs a plain + /// TCP connect, making the forwarding path reachable without a network + /// namespace. + async fn authenticated_test_client() -> russh::client::Handle { + // Scoped so the `!Send` ThreadRng is dropped before the first await. + let host_key = { + let mut rng = rand::rng(); + PrivateKey::random(&mut rng, Algorithm::Ed25519).expect("host key") + }; + let mut server_config = russh::server::Config { + auth_rejection_time: Duration::from_millis(1), + ..Default::default() + }; + server_config.keys.push(host_key); + + let handler = SshHandler::new( + forwarding_test_policy(), + ResolvedWorkspace::default(), + None, + None, + None, + ProviderCredentialState::from_child_env_snapshot(0, HashMap::new()), + HashMap::new(), + ResolvedProcessIdentity::default(), + ProcessEnforcementMode::NetworkOnly, + ); + + let (server_stream, client_stream) = tokio::io::duplex(64 * 1024); + tokio::spawn(async move { + if let Ok(session) = + russh::server::run_stream(Arc::new(server_config), server_stream, handler).await + { + let _ = session.await; + } + }); + + let mut client = russh::client::connect_stream( + Arc::new(russh::client::Config::default()), + client_stream, + AcceptAnyServerKey, + ) + .await + .expect("SSH handshake should complete over the duplex"); + + let auth = client + .authenticate_none("sandbox") + .await + .expect("auth_none should not error"); + assert!( + matches!(auth, russh::client::AuthResult::Success), + "sandbox SSH server accepts the none auth method" + ); + + client + } + + #[tokio::test] + async fn direct_tcpip_rejects_non_loopback_destination() { + let client = authenticated_test_client().await; + + let err = client + .channel_open_direct_tcpip("10.0.0.1", 80, "127.0.0.1", 0) + .await + .expect_err("forwarding to a non-loopback host must be refused"); + + assert!( + matches!( + err, + russh::Error::ChannelOpenFailure(ChannelOpenFailure::AdministrativelyProhibited) + ), + "expected AdministrativelyProhibited, got {err:?}" + ); + } + + #[tokio::test] + async fn direct_tcpip_rejects_port_above_tcp_range() { + let client = authenticated_test_client().await; + + // 65_537 truncates to port 1 when cast to u16, so the guard has to + // reject it before the cast rather than forward to a privileged port. + let err = client + .channel_open_direct_tcpip("127.0.0.1", 65_537, "127.0.0.1", 0) + .await + .expect_err("a port outside the TCP range must be refused"); + + assert!( + matches!( + err, + russh::Error::ChannelOpenFailure(ChannelOpenFailure::AdministrativelyProhibited) + ), + "expected AdministrativelyProhibited, got {err:?}" + ); + } + + #[tokio::test] + async fn direct_tcpip_forwards_to_loopback_listener() { + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + + let listener = tokio::net::TcpListener::bind("127.0.0.1:0") + .await + .expect("bind loopback echo listener"); + let port = listener.local_addr().expect("listener address").port(); + tokio::spawn(async move { + if let Ok((mut socket, _)) = listener.accept().await { + let mut buf = [0u8; 64]; + if let Ok(n) = socket.read(&mut buf).await + && n > 0 + { + let _ = socket.write_all(&buf[..n]).await; + } + } + }); + + let client = authenticated_test_client().await; + let channel = client + .channel_open_direct_tcpip("127.0.0.1", u32::from(port), "127.0.0.1", 0) + .await + .expect("forwarding to a loopback listener must be allowed"); + + let mut stream = channel.into_stream(); + stream.write_all(b"ping").await.expect("write to channel"); + + let mut echoed = [0u8; 4]; + tokio::time::timeout(Duration::from_secs(10), stream.read_exact(&mut echoed)) + .await + .expect("relayed response should arrive before the timeout") + .expect("read from channel"); + assert_eq!(&echoed, b"ping", "bytes round-trip through the tunnel"); + } } diff --git a/scripts/lint-mermaid/package-lock.json b/scripts/lint-mermaid/package-lock.json index a28a4f295..1fa73b21e 100644 --- a/scripts/lint-mermaid/package-lock.json +++ b/scripts/lint-mermaid/package-lock.json @@ -44,42 +44,10 @@ "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", "license": "MIT" }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-12.0.0.tgz", - "integrity": "sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "12.0.0", - "@chevrotain/types": "12.0.0" - } - }, - "node_modules/@chevrotain/gast": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-12.0.0.tgz", - "integrity": "sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "12.0.0" - } - }, - "node_modules/@chevrotain/regexp-to-ast": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-12.0.0.tgz", - "integrity": "sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==", - "license": "Apache-2.0" - }, "node_modules/@chevrotain/types": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-12.0.0.tgz", - "integrity": "sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-12.0.0.tgz", - "integrity": "sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==", - "license": "Apache-2.0" + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==" }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", @@ -209,12 +177,11 @@ } }, "node_modules/@mermaid-js/parser": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.0.tgz", - "integrity": "sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==", - "license": "MIT", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.0.tgz", + "integrity": "sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==", "dependencies": { - "langium": "^4.0.0" + "@chevrotain/types": "~11.1.2" } }, "node_modules/@types/d3": { @@ -533,34 +500,6 @@ "node": ">= 0.4" } }, - "node_modules/chevrotain": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-12.0.0.tgz", - "integrity": "sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/cst-dts-gen": "12.0.0", - "@chevrotain/gast": "12.0.0", - "@chevrotain/regexp-to-ast": "12.0.0", - "@chevrotain/types": "12.0.0", - "@chevrotain/utils": "12.0.0" - }, - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/chevrotain-allstar": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.4.1.tgz", - "integrity": "sha512-PvVJm3oGqrveUVW2Vt/eZGeiAIsJszYweUcYwcskg9e+IubNYKKD+rHHem7A6XVO22eDAL+inxNIGAzZ/VIWlA==", - "license": "MIT", - "dependencies": { - "lodash-es": "^4.17.21" - }, - "peerDependencies": { - "chevrotain": "^12.0.0" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -617,10 +556,9 @@ "license": "MIT" }, "node_modules/cytoscape": { - "version": "3.33.2", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.2.tgz", - "integrity": "sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==", - "license": "MIT", + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.0.tgz", + "integrity": "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==", "engines": { "node": ">=0.10" } @@ -1255,17 +1193,21 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.50.0.tgz", + "integrity": "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==" + }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -1363,10 +1305,9 @@ } }, "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", - "license": "MIT", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dependencies": { "function-bind": "^1.1.2" }, @@ -1509,24 +1450,6 @@ "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" }, - "node_modules/langium": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.2.tgz", - "integrity": "sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==", - "license": "MIT", - "dependencies": { - "@chevrotain/regexp-to-ast": "~12.0.0", - "chevrotain": "~12.0.0", - "chevrotain-allstar": "~0.4.1", - "vscode-languageserver": "~9.0.1", - "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.1.0" - }, - "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" - } - }, "node_modules/layout-base": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", @@ -1567,32 +1490,31 @@ } }, "node_modules/mermaid": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.14.0.tgz", - "integrity": "sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==", - "license": "MIT", + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.16.0.tgz", + "integrity": "sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==", "dependencies": { - "@braintree/sanitize-url": "^7.1.1", + "@braintree/sanitize-url": "^7.1.2", "@iconify/utils": "^3.0.2", - "@mermaid-js/parser": "^1.1.0", + "@mermaid-js/parser": "^1.2.0", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", - "cytoscape": "^3.33.1", + "cytoscape": "^3.33.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", - "dayjs": "^1.11.19", - "dompurify": "^3.3.1", - "katex": "^0.16.25", + "dayjs": "^1.11.20", + "dompurify": "^3.3.3", + "es-toolkit": "^1.45.1", + "katex": "^0.16.45", "khroma": "^2.1.0", - "lodash-es": "^4.17.23", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", - "uuid": "^11.1.0" + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "node_modules/mime-db": { @@ -1833,67 +1755,17 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", + "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], - "license": "MIT", "bin": { - "uuid": "dist/esm/bin/uuid" + "uuid": "dist-node/bin/uuid" } }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/vscode-languageserver": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", - "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "3.17.5" - }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "license": "MIT" - }, - "node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "license": "MIT" - }, - "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "license": "MIT" - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -1951,10 +1823,9 @@ } }, "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", - "license": "MIT", + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.2.tgz", + "integrity": "sha512-54dMVAo4WIe6SKy3vBgN+9bJZqqQ8IMRevAkOLQALhi49qkkQDQfWdAZ8KQlXiEabw88ARXXdUrlvtbKQX+aKw==", "engines": { "node": ">=10.0.0" }, From d85339d621e0e96697499a9d4c8780ee9b9c1324 Mon Sep 17 00:00:00 2001 From: Simon Scatton <44714756+SDAChess@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:40:24 +0200 Subject: [PATCH 6/8] build(bazel): add credential driver targets (#2649) * build(bazel): add credential driver targets Signed-off-by: Simon Scatton * fix(bazel): sync default CA root features Signed-off-by: Simon Scatton --------- Signed-off-by: Simon Scatton --- crates/openshell-core/src/proto/mod.rs | 22 ++++++---- .../openshell-driver-db-credstore/BUILD.bazel | 27 ++++++++++++ .../BUILD.bazel | 41 +++++++++++++++++++ crates/openshell-driver-vault/BUILD.bazel | 41 +++++++++++++++++++ crates/openshell-sandbox/BUILD.bazel | 10 ++++- .../openshell-supervisor-network/BUILD.bazel | 2 + 6 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 crates/openshell-driver-db-credstore/BUILD.bazel create mode 100644 crates/openshell-driver-kubernetes-secrets/BUILD.bazel create mode 100644 crates/openshell-driver-vault/BUILD.bazel diff --git a/crates/openshell-core/src/proto/mod.rs b/crates/openshell-core/src/proto/mod.rs index 4a9e11715..7a0f87c83 100644 --- a/crates/openshell-core/src/proto/mod.rs +++ b/crates/openshell-core/src/proto/mod.rs @@ -46,16 +46,20 @@ pub mod compute { pub use super::generated::openshell::compute::v1; } +#[allow( + clippy::all, + clippy::pedantic, + clippy::nursery, + dead_code, + unused_imports, + unused_qualifications, + rust_2018_idioms +)] pub mod credentials { - #[allow( - clippy::all, - clippy::pedantic, - clippy::nursery, - dead_code, - unused_imports, - unused_qualifications, - rust_2018_idioms - )] + #[cfg(bazel)] + pub use super::generated::openshell::credentials::v1; + + #[cfg(not(bazel))] pub mod v1 { include!(concat!(env!("OUT_DIR"), "/openshell.credentials.v1.rs")); } diff --git a/crates/openshell-driver-db-credstore/BUILD.bazel b/crates/openshell-driver-db-credstore/BUILD.bazel new file mode 100644 index 000000000..ec5e01f7f --- /dev/null +++ b/crates/openshell-driver-db-credstore/BUILD.bazel @@ -0,0 +1,27 @@ +load("@crates//:defs.bzl", "aliases", "all_crate_deps") +load("@rules_rs//rs:rust_library.bzl", "rust_library") +load("@rules_rs//rs:rust_test.bzl", "rust_test") +load("@rules_rust//rust:defs.bzl", "rustfmt_test") + +rust_library( + name = "openshell-driver-db-credstore", + srcs = glob(["src/**/*.rs"]), + aliases = aliases(), + visibility = ["//visibility:public"], + deps = all_crate_deps(normal = True), +) + +rust_test( + name = "openshell-driver-db-credstore_test", + crate = ":openshell-driver-db-credstore", + deps = all_crate_deps(normal_dev = True), +) + +rustfmt_test( + name = "rustfmt_test", + targets = [ + ":openshell-driver-db-credstore", + ":openshell-driver-db-credstore_test", + ], + visibility = ["//crates:__pkg__"], +) diff --git a/crates/openshell-driver-kubernetes-secrets/BUILD.bazel b/crates/openshell-driver-kubernetes-secrets/BUILD.bazel new file mode 100644 index 000000000..383932b55 --- /dev/null +++ b/crates/openshell-driver-kubernetes-secrets/BUILD.bazel @@ -0,0 +1,41 @@ +load("@crates//:defs.bzl", "aliases", "all_crate_deps") +load("@rules_rs//rs:rust_binary.bzl", "rust_binary") +load("@rules_rs//rs:rust_library.bzl", "rust_library") +load("@rules_rs//rs:rust_test.bzl", "rust_test") +load("@rules_rust//rust:defs.bzl", "rustfmt_test") + +rust_library( + name = "openshell-driver-kubernetes-secrets", + srcs = glob( + ["src/**/*.rs"], + exclude = ["src/main.rs"], + ), + aliases = aliases(), + visibility = ["//visibility:public"], + deps = all_crate_deps(normal = True), +) + +rust_binary( + name = "openshell-driver-kubernetes-secrets_bin", + srcs = ["src/main.rs"], + aliases = aliases(), + binary_name = "openshell-driver-kubernetes-secrets", + visibility = ["//visibility:public"], + deps = all_crate_deps(normal = True) + [":openshell-driver-kubernetes-secrets"], +) + +rust_test( + name = "openshell-driver-kubernetes-secrets_test", + crate = ":openshell-driver-kubernetes-secrets", + deps = all_crate_deps(normal_dev = True), +) + +rustfmt_test( + name = "rustfmt_test", + targets = [ + ":openshell-driver-kubernetes-secrets", + ":openshell-driver-kubernetes-secrets_bin", + ":openshell-driver-kubernetes-secrets_test", + ], + visibility = ["//crates:__pkg__"], +) diff --git a/crates/openshell-driver-vault/BUILD.bazel b/crates/openshell-driver-vault/BUILD.bazel new file mode 100644 index 000000000..3743e911b --- /dev/null +++ b/crates/openshell-driver-vault/BUILD.bazel @@ -0,0 +1,41 @@ +load("@crates//:defs.bzl", "aliases", "all_crate_deps") +load("@rules_rs//rs:rust_binary.bzl", "rust_binary") +load("@rules_rs//rs:rust_library.bzl", "rust_library") +load("@rules_rs//rs:rust_test.bzl", "rust_test") +load("@rules_rust//rust:defs.bzl", "rustfmt_test") + +rust_library( + name = "openshell-driver-vault", + srcs = glob( + ["src/**/*.rs"], + exclude = ["src/main.rs"], + ), + aliases = aliases(), + visibility = ["//visibility:public"], + deps = all_crate_deps(normal = True), +) + +rust_binary( + name = "openshell-driver-vault_bin", + srcs = ["src/main.rs"], + aliases = aliases(), + binary_name = "openshell-driver-vault", + visibility = ["//visibility:public"], + deps = all_crate_deps(normal = True) + [":openshell-driver-vault"], +) + +rust_test( + name = "openshell-driver-vault_test", + crate = ":openshell-driver-vault", + deps = all_crate_deps(normal_dev = True), +) + +rustfmt_test( + name = "rustfmt_test", + targets = [ + ":openshell-driver-vault", + ":openshell-driver-vault_bin", + ":openshell-driver-vault_test", + ], + visibility = ["//crates:__pkg__"], +) diff --git a/crates/openshell-sandbox/BUILD.bazel b/crates/openshell-sandbox/BUILD.bazel index 3b4c74b96..dac448831 100644 --- a/crates/openshell-sandbox/BUILD.bazel +++ b/crates/openshell-sandbox/BUILD.bazel @@ -12,7 +12,10 @@ rust_library( exclude = ["src/main.rs"], ), aliases = aliases(), - crate_features = ["telemetry"], + crate_features = [ + "bundled-ca-roots", + "telemetry", + ], version = WORKSPACE_VERSION, visibility = ["//visibility:public"], deps = all_crate_deps(normal = True), @@ -33,7 +36,10 @@ rust_test( name = "openshell-sandbox_lib_test", compile_data = ["//crates/openshell-supervisor-network:sandbox-policy-rego"], crate = ":openshell-sandbox", - crate_features = ["telemetry"], + crate_features = [ + "bundled-ca-roots", + "telemetry", + ], deps = all_crate_deps(normal_dev = True), ) diff --git a/crates/openshell-supervisor-network/BUILD.bazel b/crates/openshell-supervisor-network/BUILD.bazel index fe3673a6b..851b71958 100644 --- a/crates/openshell-supervisor-network/BUILD.bazel +++ b/crates/openshell-supervisor-network/BUILD.bazel @@ -14,6 +14,7 @@ rust_library( srcs = glob(["src/**/*.rs"]), aliases = aliases(), compile_data = glob(["data/**/*"]), + crate_features = ["bundled-ca-roots"], visibility = ["//visibility:public"], deps = all_crate_deps(normal = True), ) @@ -22,6 +23,7 @@ rust_test( name = "openshell-supervisor-network_test", compile_data = glob(["testdata/**/*"]), crate = ":openshell-supervisor-network", + crate_features = ["bundled-ca-roots"], deps = all_crate_deps(normal_dev = True), ) From 8ddd98c3dff62619a3963f99ba1e055b67650e72 Mon Sep 17 00:00:00 2001 From: Simon Scatton <44714756+SDAChess@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:40:28 +0200 Subject: [PATCH 7/8] feat(bazel): build vm driver and pull runtime from Github (#2650) * build(bazel): stage VM runtime bundle Signed-off-by: Simon Scatton * build(bazel): add VM driver targets Signed-off-by: Simon Scatton --------- Signed-off-by: Simon Scatton --- MODULE.bazel | 50 +++++++++++++++++ bazel/BUILD.bazel | 5 +- bazel/releases/BUILD.bazel | 2 + bazel/vm-runtime/BUILD.bazel | 72 +++++++++++++++++++++++++ bazel/vm_runtime.bzl | 46 ++++++++++++++++ crates/openshell-driver-vm/BUILD.bazel | 74 ++++++++++++++++++++++++++ crates/openshell-vfio/BUILD.bazel | 1 + 7 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 bazel/vm-runtime/BUILD.bazel create mode 100644 bazel/vm_runtime.bzl create mode 100644 crates/openshell-driver-vm/BUILD.bazel diff --git a/MODULE.bazel b/MODULE.bazel index 5946d072f..d54b7ba40 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -6,6 +6,56 @@ bazel_dep(name = "rules_cc", version = "0.2.20") bazel_dep(name = "rules_proto", version = "7.1.0") bazel_dep(name = "protobuf", version = "34.0.bcr.1") +bazel_lib_toolchains = use_extension("@bazel_lib//lib:extensions.bzl", "toolchains") +bazel_lib_toolchains.zstd() +use_repo(bazel_lib_toolchains, "zstd_toolchains") + +register_toolchains("@zstd_toolchains//:all") + +http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "vm_runtime_darwin_aarch64", + build_file_content = """ +exports_files([ + "gvproxy", + "libkrun.dylib", + "libkrunfw.5.dylib", + "umoci", +]) +""", + integrity = "sha256-KSAKryBFZiytwYwXB0CR++u4tGlA5ficf7X/nen7qQE=", + urls = ["https://github.com/NVIDIA/OpenShell/releases/download/vm-runtime/vm-runtime-darwin-aarch64.tar.zst"], +) + +http_archive( + name = "vm_runtime_linux_aarch64", + build_file_content = """ +exports_files([ + "gvproxy", + "libkrun.so", + "libkrunfw.so.5", + "umoci", +]) +""", + integrity = "sha256-zn0P4NtEKp7Euy44HfQ3YoBNijF2VWFTaLLsase+Y1A=", + urls = ["https://github.com/NVIDIA/OpenShell/releases/download/vm-runtime/vm-runtime-linux-aarch64.tar.zst"], +) + +http_archive( + name = "vm_runtime_linux_x86_64", + build_file_content = """ +exports_files([ + "gvproxy", + "libkrun.so", + "libkrunfw.so.5", + "umoci", +]) +""", + integrity = "sha256-urdMjarDN5WJ6eCmpddlcnEn2v3qp28D7+e6PtyhPr0=", + urls = ["https://github.com/NVIDIA/OpenShell/releases/download/vm-runtime/vm-runtime-linux-x86_64.tar.zst"], +) + include("//bazel/annotations:aws-lc-sys.MODULE.bazel") include("//bazel/annotations:z3-sys.MODULE.bazel") include("//bazel/annotations:zstd-sys.MODULE.bazel") diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel index 8d61bb55c..ceba7d2e2 100644 --- a/bazel/BUILD.bazel +++ b/bazel/BUILD.bazel @@ -1 +1,4 @@ -exports_files(["cargo_version.bzl"]) +exports_files([ + "cargo_version.bzl", + "vm_runtime.bzl", +]) diff --git a/bazel/releases/BUILD.bazel b/bazel/releases/BUILD.bazel index e424cef40..bc859ed16 100644 --- a/bazel/releases/BUILD.bazel +++ b/bazel/releases/BUILD.bazel @@ -44,6 +44,7 @@ platform_transition_binary( binary = "//crates/openshell-sandbox:openshell-sandbox-bin", tags = ["manual"], target_platform = "@rules_rs//rs/platforms:x86_64-unknown-linux-musl", + visibility = ["//visibility:public"], ) platform_transition_binary( @@ -52,6 +53,7 @@ platform_transition_binary( binary = "//crates/openshell-sandbox:openshell-sandbox-bin", tags = ["manual"], target_platform = "@rules_rs//rs/platforms:aarch64-unknown-linux-musl", + visibility = ["//visibility:public"], ) platform_transition_binary( diff --git a/bazel/vm-runtime/BUILD.bazel b/bazel/vm-runtime/BUILD.bazel new file mode 100644 index 000000000..073bcf047 --- /dev/null +++ b/bazel/vm-runtime/BUILD.bazel @@ -0,0 +1,72 @@ +load("//bazel:vm_runtime.bzl", "vm_runtime_bundle") + +config_setting( + name = "darwin_aarch64", + constraint_values = [ + "@platforms//cpu:aarch64", + "@platforms//os:osx", + ], +) + +config_setting( + name = "linux_aarch64", + constraint_values = [ + "@platforms//cpu:aarch64", + "@platforms//os:linux", + ], +) + +config_setting( + name = "linux_x86_64", + constraint_values = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + ], +) + +vm_runtime_bundle( + name = "runtime", + gvproxy = select({ + ":darwin_aarch64": "@vm_runtime_darwin_aarch64//:gvproxy", + ":linux_aarch64": "@vm_runtime_linux_aarch64//:gvproxy", + ":linux_x86_64": "@vm_runtime_linux_x86_64//:gvproxy", + }), + libkrun = select({ + ":darwin_aarch64": "@vm_runtime_darwin_aarch64//:libkrun.dylib", + ":linux_aarch64": "@vm_runtime_linux_aarch64//:libkrun.so", + ":linux_x86_64": "@vm_runtime_linux_x86_64//:libkrun.so", + }), + libkrun_name = select({ + ":darwin_aarch64": "libkrun.dylib", + ":linux_aarch64": "libkrun.so", + ":linux_x86_64": "libkrun.so", + }), + libkrunfw = select({ + ":darwin_aarch64": "@vm_runtime_darwin_aarch64//:libkrunfw.5.dylib", + ":linux_aarch64": "@vm_runtime_linux_aarch64//:libkrunfw.so.5", + ":linux_x86_64": "@vm_runtime_linux_x86_64//:libkrunfw.so.5", + }), + libkrunfw_name = select({ + ":darwin_aarch64": "libkrunfw.5.dylib", + ":linux_aarch64": "libkrunfw.so.5", + ":linux_x86_64": "libkrunfw.so.5", + }), + supervisor = select({ + ":darwin_aarch64": "//bazel/releases:openshell_sandbox_linux_aarch64", + ":linux_aarch64": "//bazel/releases:openshell_sandbox_linux_aarch64", + ":linux_x86_64": "//bazel/releases:openshell_sandbox_linux_x86_64", + }), + tags = ["manual"], + target_compatible_with = select({ + ":darwin_aarch64": [], + ":linux_aarch64": [], + ":linux_x86_64": [], + "//conditions:default": ["@platforms//:incompatible"], + }), + umoci = select({ + ":darwin_aarch64": "@vm_runtime_darwin_aarch64//:umoci", + ":linux_aarch64": "@vm_runtime_linux_aarch64//:umoci", + ":linux_x86_64": "@vm_runtime_linux_x86_64//:umoci", + }), + visibility = ["//visibility:public"], +) diff --git a/bazel/vm_runtime.bzl b/bazel/vm_runtime.bzl new file mode 100644 index 000000000..1f611fd44 --- /dev/null +++ b/bazel/vm_runtime.bzl @@ -0,0 +1,46 @@ +"""Rules for staging the embedded openshell-driver-vm runtime.""" + +_ZSTD_TOOLCHAIN = "@bazel_lib//lib:zstd_toolchain_type" + +def _vm_runtime_bundle_impl(ctx): + output = ctx.actions.declare_directory(ctx.label.name) + zstd = ctx.toolchains[_ZSTD_TOOLCHAIN].zstdinfo.binary + + resources = [ + (ctx.file.libkrun, ctx.attr.libkrun_name), + (ctx.file.libkrunfw, ctx.attr.libkrunfw_name), + (ctx.file.gvproxy, "gvproxy"), + (ctx.executable.supervisor, "openshell-sandbox"), + (ctx.file.umoci, "umoci"), + ] + commands = ["mkdir -p '{}'".format(output.path)] + for source, name in resources: + commands.append("'{}' -q -f '{}' -o '{}/{}.zst'".format( + zstd.path, + source.path, + output.path, + name, + )) + + ctx.actions.run_shell( + command = "set -euo pipefail\n{}".format("\n".join(commands)), + inputs = [source for source, _ in resources], + outputs = [output], + tools = [zstd], + ) + + return [DefaultInfo(files = depset([output]))] + +vm_runtime_bundle = rule( + implementation = _vm_runtime_bundle_impl, + attrs = { + "gvproxy": attr.label(allow_single_file = True, mandatory = True), + "libkrun": attr.label(allow_single_file = True, mandatory = True), + "libkrun_name": attr.string(mandatory = True), + "libkrunfw": attr.label(allow_single_file = True, mandatory = True), + "libkrunfw_name": attr.string(mandatory = True), + "supervisor": attr.label(executable = True, cfg = "target", mandatory = True), + "umoci": attr.label(allow_single_file = True, mandatory = True), + }, + toolchains = [_ZSTD_TOOLCHAIN], +) diff --git a/crates/openshell-driver-vm/BUILD.bazel b/crates/openshell-driver-vm/BUILD.bazel new file mode 100644 index 000000000..d433b7842 --- /dev/null +++ b/crates/openshell-driver-vm/BUILD.bazel @@ -0,0 +1,74 @@ +load("@crates//:defs.bzl", "aliases", "all_crate_deps") +load("@rules_rs//rs:rust_binary.bzl", "rust_binary") +load("@rules_rs//rs:rust_library.bzl", "rust_library") +load("@rules_rs//rs:rust_test.bzl", "rust_test") +load("@rules_rust//rust:defs.bzl", "rustfmt_test") +load("@workspace_version//:version.bzl", "WORKSPACE_VERSION") + +VM_RUNTIME = "//bazel/vm-runtime:runtime" + +VM_RUNTIME_ENV = { + "OUT_DIR": "$(execpath //bazel/vm-runtime:runtime)", +} + +rust_library( + name = "openshell-driver-vm", + srcs = glob( + ["src/**/*.rs"], + exclude = ["src/main.rs"], + ), + aliases = aliases(), + compile_data = [ + "scripts/openshell-vm-sandbox-init.sh", + VM_RUNTIME, + ], + crate_features = ["telemetry"], + rustc_env = VM_RUNTIME_ENV, + version = WORKSPACE_VERSION, + visibility = ["//visibility:public"], + deps = all_crate_deps(normal = True), +) + +rust_binary( + name = "openshell-driver-vm_bin", + srcs = ["src/main.rs"], + aliases = aliases(), + binary_name = "openshell-driver-vm", + version = WORKSPACE_VERSION, + visibility = ["//visibility:public"], + deps = all_crate_deps(normal = True) + [":openshell-driver-vm"], +) + +rust_test( + name = "openshell-driver-vm_lib_test", + compile_data = [ + "scripts/openshell-vm-sandbox-init.sh", + VM_RUNTIME, + ], + crate = ":openshell-driver-vm", + crate_features = ["telemetry"], + rustc_env = VM_RUNTIME_ENV, + deps = all_crate_deps(normal_dev = True), +) + +rust_test( + name = "openshell-driver-vm_bin_test", + srcs = ["src/main.rs"], + aliases = aliases(), + version = WORKSPACE_VERSION, + deps = all_crate_deps( + normal = True, + normal_dev = True, + ) + [":openshell-driver-vm"], +) + +rustfmt_test( + name = "rustfmt_test", + targets = [ + ":openshell-driver-vm", + ":openshell-driver-vm_bin", + ":openshell-driver-vm_bin_test", + ":openshell-driver-vm_lib_test", + ], + visibility = ["//crates:__pkg__"], +) diff --git a/crates/openshell-vfio/BUILD.bazel b/crates/openshell-vfio/BUILD.bazel index 1e08c0bb9..0ec5f0679 100644 --- a/crates/openshell-vfio/BUILD.bazel +++ b/crates/openshell-vfio/BUILD.bazel @@ -9,6 +9,7 @@ rust_library( srcs = glob(["src/**/*.rs"]), aliases = aliases(), target_compatible_with = ["@platforms//os:linux"], + visibility = ["//visibility:public"], deps = all_crate_deps(normal = True), ) From 4cb77a900ebd6b789d2b68daaba4830866833b1c Mon Sep 17 00:00:00 2001 From: Matthew Grossman Date: Fri, 7 Aug 2026 10:39:44 -0700 Subject: [PATCH 8/8] fix(e2e): separate Podman Machine loopback listeners (#2622) * fix(e2e): separate Podman Machine loopback listeners Signed-off-by: Matthew Grossman * test(e2e): remove shallow harness checks Signed-off-by: Matthew Grossman * refactor(e2e): trim Podman listener workaround Signed-off-by: Matthew Grossman * fix(e2e): bypass proxies for Podman health probe Signed-off-by: Matthew Grossman --------- Signed-off-by: Matthew Grossman --- .../skills/debug-openshell-cluster/SKILL.md | 10 ++++-- crates/openshell-core/src/forward.rs | 17 +++++++--- docs/reference/sandbox-compute-drivers.mdx | 6 ++-- e2e/with-podman-gateway.sh | 32 +++++++++++++------ 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/.agents/skills/debug-openshell-cluster/SKILL.md b/.agents/skills/debug-openshell-cluster/SKILL.md index 319031b1d..81cc67969 100644 --- a/.agents/skills/debug-openshell-cluster/SKILL.md +++ b/.agents/skills/debug-openshell-cluster/SKILL.md @@ -208,9 +208,13 @@ Common findings: host's IPv4 default route. Rootless pasta uses the private source address selected by that route; rootful Podman uses the bridge gateway address. - Callback discovery reports that the requested address equals the primary - listener: configure a distinct primary address. For Podman Machine, keep the - IPv4 loopback callback separate by using an IPv6-loopback primary such as - `[::1]:17670`. + listener: configure a distinct primary address. For Podman Machine, bind the + primary listener to IPv6 loopback, for example + `bind_address = "[::1]:17670"`, and register the CLI endpoint as + `https://localhost:17670`. The generated certificate includes `localhost`, + while a raw `https://[::1]:17670` endpoint can fail TLS setup with + `invalid dns name`. This leaves `127.0.0.1:17670` available for the + callback-only listener. - Rootless slirp4netns, another named helper, or missing helper metadata requires an explicitly remote `grpc_endpoint`. An explicit `host_gateway_ip` cannot bypass slirp4netns host-loopback isolation. Do not work around diff --git a/crates/openshell-core/src/forward.rs b/crates/openshell-core/src/forward.rs index 1d97174d9..3b9527bcc 100644 --- a/crates/openshell-core/src/forward.rs +++ b/crates/openshell-core/src/forward.rs @@ -745,11 +745,11 @@ pub fn resolve_ssh_gateway( // Remote cluster: use the remote host but keep the cluster URL port. return (host.to_string(), cluster_port); } - // Both endpoints loopback. The unspecified addresses (0.0.0.0 / ::) - // are bind-only — they aren't valid connect targets and aren't in TLS - // cert SANs, so fall back to the cluster URL's host (which the CLI - // is already using to reach the gateway). - if gateway_host == "0.0.0.0" || gateway_host == "::" { + // Unspecified addresses are bind-only, and tonic cannot use an IPv6 + // literal as a TLS DNS name. In those cases, keep the cluster URL's + // already-reachable authority. Other loopback addresses retain the + // gateway-reported host. + if matches!(gateway_host, "0.0.0.0" | "::" | "::1") { return (host.to_string(), cluster_port); } return (gateway_host.to_string(), cluster_port); @@ -1026,6 +1026,13 @@ mod tests { assert_eq!(port, 443); } + #[test] + fn resolve_ssh_gateway_preserves_loopback_tls_authority() { + let (host, port) = resolve_ssh_gateway("::1", 8080, "https://localhost:8443"); + assert_eq!(host, "localhost"); + assert_eq!(port, 8443); + } + #[test] fn resolve_ssh_gateway_swaps_zeros_for_loopback_cluster_host() { // The gateway binds 0.0.0.0 but advertises that bind address via the diff --git a/docs/reference/sandbox-compute-drivers.mdx b/docs/reference/sandbox-compute-drivers.mdx index 2132f3360..ea4c1a37b 100644 --- a/docs/reference/sandbox-compute-drivers.mdx +++ b/docs/reference/sandbox-compute-drivers.mdx @@ -114,8 +114,10 @@ reflection, inference-route management, and HTTP requests. A is expected for those requests. The gateway fails startup if a callback requirement resolves to the exact primary listener address because one socket cannot preserve both authorization scopes. For the IPv4-loopback callback used -by Podman Machine, bind the primary listener to a distinct address such as -`[::1]:17670`. +by Podman Machine, set `bind_address = "[::1]:17670"` for the primary listener +and register `https://localhost:17670` as the CLI endpoint. The hostname matches +the generated certificate and avoids the TLS transport error produced by a raw +IPv6-literal endpoint. Do not broaden the primary listener to `0.0.0.0`. ## Docker Driver diff --git a/e2e/with-podman-gateway.sh b/e2e/with-podman-gateway.sh index 8c598c88f..cd52e007a 100755 --- a/e2e/with-podman-gateway.sh +++ b/e2e/with-podman-gateway.sh @@ -386,6 +386,16 @@ export OPENSHELL_E2E_GATEWAY_CA_CERT="${PKI_DIR}/ca.crt" HOST_PORT=$(e2e_pick_port) HEALTH_PORT=$(e2e_pick_port) +if [ "$(uname -s)" = "Darwin" ]; then + # Podman Machine reserves IPv4 loopback for its callback-only listener. + PRIMARY_BIND_IP="::1" + CLI_ENDPOINT_HOST="localhost" + HEALTH_ENDPOINT_HOST="[::1]" +else + PRIMARY_BIND_IP="127.0.0.1" + CLI_ENDPOINT_HOST="127.0.0.1" + HEALTH_ENDPOINT_HOST="127.0.0.1" +fi STATE_DIR="${WORKDIR}/state" mkdir -p "${STATE_DIR}" export XDG_STATE_HOME="${STATE_DIR}" @@ -415,11 +425,11 @@ toml_string() { GATEWAY_CONFIG="${STATE_DIR}/gateway.toml" -# Start from the RPM default template so this e2e test exercises the same -# TOML config path that RPM users get on first start. The template leaves -# bind_address unset and sets compute_drivers = ["podman"], so this test -# exercises the built-in loopback listener plus the callback listener -# requested by the Podman driver. +# Start from the RPM default template so this e2e test exercises the same TOML +# config path that RPM users get on first start. The template leaves +# bind_address unset and sets compute_drivers = ["podman"]. On Podman Machine, +# the driver reserves IPv4 loopback for its callback-only listener, so the +# primary listener uses IPv6 loopback. Native Linux keeps the IPv4 default. # # We append the driver-specific table and override the port via CLI flag # (CLI > TOML in the merge precedence) so the test can use an ephemeral port. @@ -458,8 +468,9 @@ cp "${ROOT}/deploy/rpm/gateway.toml.default" "${GATEWAY_CONFIG}" GATEWAY_ARGS=( --config "${GATEWAY_CONFIG}" - # compute_drivers comes from the RPM template, while bind_address uses the - # built-in loopback default. Override only the port for ephemeral selection. + # compute_drivers comes from the RPM template. Override the loopback address + # and port so Podman Machine can keep its IPv4 callback listener distinct. + --bind-address "${PRIMARY_BIND_IP}" --port "${HOST_PORT}" --health-port "${HEALTH_PORT}" --tls-cert "${PKI_DIR}/server/tls.crt" @@ -495,10 +506,10 @@ printf '%s\n' "${GATEWAY_PID}" >"${GATEWAY_PID_FILE}" GATEWAY_NAME="openshell-e2e-podman-${HOST_PORT}" if [ "${OIDC_MODE}" = "1" ]; then - CLI_GATEWAY_ENDPOINT="https://127.0.0.1:${HOST_PORT}" + CLI_GATEWAY_ENDPOINT="https://${CLI_ENDPOINT_HOST}:${HOST_PORT}" export OPENSHELL_E2E_OIDC_GATEWAY_ENDPOINT="${CLI_GATEWAY_ENDPOINT}" else - CLI_GATEWAY_ENDPOINT="https://127.0.0.1:${HOST_PORT}" + CLI_GATEWAY_ENDPOINT="https://${CLI_ENDPOINT_HOST}:${HOST_PORT}" e2e_register_mtls_gateway \ "${XDG_CONFIG_HOME}" \ "${GATEWAY_NAME}" \ @@ -524,7 +535,8 @@ while [ "${elapsed}" -lt "${timeout}" ]; do echo "ERROR: openshell-gateway exited before becoming healthy" exit 1 fi - if curl -sf "http://127.0.0.1:${HEALTH_PORT}/healthz" >/dev/null 2>&1; then + # Keep this loopback probe direct even when ::1 is absent from NO_PROXY. + if curl --noproxy '*' -sf "http://${HEALTH_ENDPOINT_HOST}:${HEALTH_PORT}/healthz" >/dev/null 2>&1; then echo "Gateway healthy after ${elapsed}s." break fi