* chore(nix): unify native and cross-compilation toolchains
Replace the separate GNU and musl development shells with one shell that
provides explicit toolchains for x86_64 and aarch64 Linux, plus native
Darwin on macOS. Cargo selects the compiler, assembler, archiver, and
linker through target-specific environment variables.
Build GNU targets against a glibc 2.28 sysroot with static GCC runtimes
and use the musl toolchains for static Linux executables. Build Z3 and
AWS-LC with each target's stdenv and expose AWS-LC libraries and Rust
bindings through its target-specific system directory.
Keep Darwin system libraries on the unprocessed Apple SDK and prevent
the Rust toolchain from propagating replacement libraries into the shell.
Include the compiler and libc fixes needed for Darwin-to-Linux builds.
Share dependency and environment wiring through mkToolchain, keep compiler
wrappers in the Linux and Darwin modules, and group the pinned GNU build
environment under glibc-2.28. Document the toolchain boundaries in the build
architecture overview.
Validation: actionlint and nix fmt pass. The toolchain refactor preserves
the shell derivations for x86_64 Linux, aarch64 Linux, and aarch64 Darwin.
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
* ci(nix): use the default shell for binary builds
Remove the dev-shell input and its matrix and workflow plumbing.
Use the host default shell for builds and cache hashing.
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
* ci(nix): build release binaries with explicit Cargo targets
Use target-specific artifact paths and rely on the Nix toolchains for
linkage. Remove post-link rewriting, platform linkage checks, and the
unused interpreter input. Update the build architecture documentation.
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
* refactor(nix): reuse glibc and GCC build recipes
Use a pinned historical Nixpkgs recipe for glibc 2.28 and rebuild GCC 15
against it instead of maintaining separate runtime builds. Keep only
compatibility adjustments needed by the current build tools.
Assemble the sysroot from glibc outputs and static native libraries. Use
standard ELF interpreters and resolve Rust's explicit gcc_s dependency
through the static GCC archives.
Allow 90 minutes for Rust branch checks to accommodate cold toolchain
builds. Validate the Linux release matrix locally; Darwin remains for CI.
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
* fix(nix): disable obsolete RPC tools in glibc
Avoid building rpcgen against the Darwin SDK, which does not expose stat64.
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
---------
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
* feat(docs): add version availability labels
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* fix(docs): use supported Python for sync
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* feat(docs): publish versioned docs from releases
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* fix(docs): format dev version label
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* chore(docs): upgrade Fern CLI to 5.112.0
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* fix(docs): make release publishing monotonic
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* fix(docs): preserve snapshot release identity
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* docs(fern): document versioned publishing
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* test(docs): cover explicit snapshot rollback
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* refactor(docs): use Fern refs for versions
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* fix(docs): bundle components for ref versions
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* revert(docs): keep complete version copies
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* fix(docs): publish latest and dev channels
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
---------
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* feat(middleware): define HTTP response pre-return interface
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* docs(middleware): clarify HTTP response interface
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* refactor(middleware): align response result actions
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* refactor(middleware): expose response reason codes
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* refactor(middleware): share session end reasons
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* feat(middleware)!: finalize HTTP response pre-return contract
Replace the separate body_end event with HttpResponseBodyUnit.end_of_stream.
Every body-inspecting stage receives exactly one flagged unit, which may be
empty; a zero-byte body is one empty flagged unit and OpenShell never reads
ahead to set the flag.
Defer response trailers from V1 and reserve their field numbers. HTTP/1.0
clients and Content-Length bodies cannot carry trailers and that behavior was
undefined.
Add HttpResponsePreflight.permitted_body_modes, computed once from the
original upstream head so every stage sees the same list, and make an
unlisted selection a failure rather than a downgrade. Add the block_delivery
preflight action as a successful decision enforced regardless of on_error.
Expose Content-Length, Content-Encoding, and Content-Range read-only in
preflight. Cap STREAM_BYTES input units at half of max_payload_bytes and
permit deferring bytes across replacements only for fail_closed bindings,
surfaced as deferral_permitted.
Split PEER_DISCONNECT into DOWNSTREAM_DISCONNECT and UPSTREAM_DISCONNECT and
attribute WebSocket relay failures by direction instead of a generic peer
error. Compile the content-guard example in lint and branch checks so proto
renames cannot break it silently.
BREAKING CHANGE: WebSocketSessionEndReason and WebSocketSessionEnd are
replaced by the shared MiddlewareSessionEndReason and MiddlewareSessionEnd.
NORMAL_CLOSE is now NORMAL, UPSTREAM_REJECTED is now UPSTREAM_FAILURE, and
PEER_DISCONNECT is split into DOWNSTREAM_DISCONNECT and UPSTREAM_DISCONNECT.
Enum numbers are unchanged so binary wire compatibility is preserved;
generated symbols and JSON names change.
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* docs(middleware): describe skip as opting out of inspection
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* feat(middleware): add body-phase block_delivery and skip_remaining actions
Body results may now stop delivery or opt out of inspecting the rest of the
response after a prefix. One HttpResponseBlockDelivery message is shared by
preflight and body results and documents the difference between blocking
before and after head commitment. Drop the field reservations, since nothing
in this contract has shipped, and renumber session_end to close the gap.
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* refactor(middleware): share HTTP body leaf messages across directions
HttpBodyUnit, HttpBodyPassThrough, HttpBodyTransform, HttpBodySkipRemaining,
and HttpBodyMode carry no response-specific semantics, so name them for reuse
by the streaming request hook. Envelopes, results, preflight, and
block_delivery stay response-specific because commitment semantics differ.
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* refactor(middleware): keep HTTP body leaf messages response-specific
Reverts the shared HttpBody* naming. A direction-specific payload such as a
response-only semantic mode would otherwise add unreachable variants to the
other direction or force a source-breaking fork after 0.1.0. The streaming
request hook defines its own HttpRequestBody* messages and copies the shape;
SDKs present a direction-neutral body handler over both.
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* docs(middleware): simplify response proto comments
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* fix(middleware): reject undispatched response bindings
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* docs(middleware): simplify phase field comment
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* refactor(middleware): rename HTTP response preflight result
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* feat(middleware): add HTTP response trailer results
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* docs(middleware): define response block delivery
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* docs(middleware): define stage-local response body modes
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* docs(middleware): define final response body units
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* docs(middleware): define streaming response deferral
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* docs(middleware): defer whole-body accumulation timeout
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* docs(middleware): align response result diagnostics
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* test(middleware): cover upstream WebSocket disconnect
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* fix(middleware): keep response streams unit-local
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* docs(middleware): trim disconnect compatibility note
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
---------
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* feat(build): add defaults-without-telemetry feature alias
Cargo cannot subtract a single default feature, so compiling telemetry out
meant `--no-default-features` plus a hand-maintained keep-list of the crate's
other defaults. That keep-list was already wrong for operators: telemetry is
the only default on openshell-server and openshell-driver-vm, but
openshell-sandbox also defaults to `bundled-ca-roots`, so a bare
`--no-default-features` silently swapped the supervisor onto the platform
trust store.
Add a `defaults-without-telemetry` alias to each of the three telemetry-
carrying binary crates, enumerating every default except `telemetry`.
Telemetry-free builds become `--no-default-features --features
defaults-without-telemetry` and stay correct as the default set grows.
The alias is a keep-list, not a switch. Enabling it on top of the defaults
would otherwise produce a telemetry-on binary that reads as telemetry-free, so
each crate root carries a `compile_error!` for the `telemetry` +
`defaults-without-telemetry` combination.
Add `rust:verify:defaults-without-telemetry` to guard both properties: each
alias still equals its crate's defaults minus `telemetry`, and the
mutual-exclusion error is wired up. The additive-misuse check matches on the
`compile_error!` text rather than a nonzero exit code so it cannot pass
vacuously on hosts where openshell-driver-vm fails to build for unrelated
reasons. `rust:verify:telemetry-off` now builds through the alias.
Signed-off-by: Russell Bryant <rbryant@redhat.com>
* fix feature alias for openshell-server
Signed-off-by: Russell Bryant <rbryant@redhat.com>
* fix(ci): run Rust verification in Nix shell
Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
---------
Signed-off-by: Russell Bryant <rbryant@redhat.com>
Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
Co-authored-by: John Myers <9696606+johntmyers@users.noreply.github.com>
Closes#3038
Compress platform runtime inputs in Nix and stage the complete bundle outside
Cargo's target directory. Reject missing or empty embedding artifacts during
the driver build.
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
* ci(branch-checks): run Rust checks in Nix shells
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
* ci(branch-checks): run Rust tests with nextest
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
* ci(branch-checks): cache Rust workspace artifacts
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
* ci(branch-checks): run cargo-deny in Nix shell
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
---------
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
The Maturin-based wheel packaging was a historical remnant from when the local gateway launch path and OpenShell CLI were coupled in one binary. The gateway and CLI now ship as standalone artifacts, so the Python distribution should contain only the SDK.
Build a single platform-independent setuptools wheel, verify that it cannot contain native code or an openshell entry point, and simplify the release jobs and documentation for SDK-only PyPI installs.
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
* feat(k8s): add namespace-per-workspace support (RFC 0011 Phase 3)
Implement three workspace namespace modes for the Kubernetes compute
driver: shared (default, preserves current single-namespace behavior),
managed (auto-creates/deletes namespaces per workspace), and operator
(pre-provisioned namespaces with dynamic discovery via label selector
or drop-in allowlist file).
Key changes:
- WorkspaceMode enum and namespace resolution in driver config
- Managed namespace lifecycle with ServiceAccount and OpenShift SCC
annotation propagation
- Cluster-wide sandbox CR watchers for managed/operator modes
- NamespaceValidator (Exact/Prefix/Allowlist) for SA token auth
- Workspace-aware credential secret storage
- Helm ClusterRole for multi-namespace RBAC
- Gateway config, architecture, and reference docs
Signed-off-by: Derek Carr <decarr@redhat.com>
* test(k8s): add e2e tests for workspace namespace modes
Add end-to-end tests for managed and operator workspace modes
introduced in RFC 0011 Phase 3. The managed mode tests verify
namespace creation with correct labels, ServiceAccount provisioning,
sandbox CR placement, and namespace survival with remaining sandboxes.
The operator mode tests verify rejection of unlabeled and nonexistent
namespaces. The positive operator path (sandbox in labeled namespace)
is known to fail due to an RBAC gap and will be addressed separately.
Also fixes Helm 4 compatibility: move SPDX license headers inside
conditional guards in 8 chart templates to prevent empty comment-only
documents, and fix a trailing whitespace trimmer in clusterrole.yaml
that concatenated the license header with apiVersion.
Adds cleanup sweep in with-kube-gateway.sh to remove managed and
operator namespaces before Helm uninstall, and mise tasks for running
each mode independently.
Signed-off-by: Derek Carr <decarr@redhat.com>
* feat(k8s): add operator namespace label watcher
Spawn a background kube::runtime::watcher in the K8s driver that
watches namespaces matching the configured label selector and populates
the OperatorNamespaceAllowlist at runtime. The driver owns the
allowlist and exposes its Arc so the server can share the same set with
the SA token authenticator.
create_sandbox now gates pod creation on the allowlist in operator
mode — workspaces whose namespace is not yet labeled are rejected at
resource render time rather than silently proceeding. Workspace
lifecycle itself is unaffected; only sandbox (resource) creation is
gated.
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(k8s): harden operator mode and address review findings
Close the fail-open gap in operator mode when only
operator_namespace_file is configured: the allowlist is now created
unconditionally in operator mode (fail-closed from startup).
Implement the namespace file watcher using the notify crate, following
the TLS hot-reload pattern (parent-directory watch, 1s debounce,
ConfigMap symlink-swap safe). The file format is a JSON array of
namespace name strings.
Additional fixes from the 10-reviewer audit:
- Change allowlist rejection from InvalidArgument to FailedPrecondition
so callers know the request may succeed later once the namespace is
provisioned.
- NamespaceValidator::Allowlist now holds the OperatorNamespaceAllowlist
newtype instead of a raw Arc<RwLock<BTreeSet>>, eliminating silent
denial on RwLock poison.
- Verify LABEL_MANAGED_BY and LABEL_GATEWAY_ID ownership before
deleting a managed namespace.
- Replace fixed 5s sleep in operator e2e test with a 30s poll loop.
- Add Helm validation for workspaceMode values.
- Fix Helm README type column and description for operator fields.
- Add insert/remove methods to OperatorNamespaceAllowlist; label
watcher now uses them instead of reaching through shared().
- Reject configs with both operator_namespace_label and
operator_namespace_file set.
Signed-off-by: Derek Carr <decarr@redhat.com>
* feat(k8s): add workspace-level compute driver RPCs and harden RBAC
Decouple namespace lifecycle from sandbox lifecycle by adding
EnsureWorkspace/DeleteWorkspace RPCs to the ComputeDriver service.
Namespace creation now happens before credential storage and namespace
deletion happens on workspace delete, fixing credential storage in
managed workspace mode.
- Add EnsureWorkspace and DeleteWorkspace proto RPCs with
implementations across all compute drivers (K8s managed delegates to
ensure_namespace/delete_namespace_if_empty; others no-op)
- Wire ensure_workspace into provider create/update/refresh paths so
the namespace exists before the credential driver writes secrets
- Wire delete_workspace into workspace deletion for cleanup
- Remove delete_namespace_if_empty from sandbox deletion path
- Scope ClusterRole secrets access to non-shared workspace modes
- Add TODO for TLS cert hot-reload in sandbox gRPC client
- Harden e2e tests with control-plane sandbox resolution assertions
- Fix docker image save --platform flag for OCI index manifests
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(k8s): address re-review findings and add test coverage
- Use server-side apply for TLS secret sync (fixes second sandbox
creation failure when TLS is enabled)
- Scope gateway-ID label selector unconditionally across all workspace
modes (fixes operator reads/watches/deletes seeing foreign sandboxes)
- Validate operator allowlist in EnsureWorkspace and DeleteWorkspace
RPCs (prevents credential writes to namespaces outside the allowlist)
- Extend ClusterRole secrets patch+delete to all non-shared modes with
credential driver enabled (fixes operator credential storage RBAC)
- Validate namespace ownership on 409 conflict in ensure_namespace
(prevents adopting unowned namespaces in managed mode)
- Replace delete_namespace_if_empty with unconditional delete_namespace
letting Kubernetes cascade cleanup (fixes stuck terminating CRs)
- Strengthen NetworkPolicy TODO to cover both managed and operator modes
- Extract selector and ownership logic into testable free functions
- Add unit tests for gateway-ID selectors and namespace ownership
- Add Helm ClusterRole RBAC tests for operator credential driver
Signed-off-by: Derek Carr <decarr@redhat.com>
* ci(k8s): add workspace managed and operator mode e2e to CI
Wire the existing e2e:kubernetes:workspace-managed and
e2e:kubernetes:workspace-operator mise tasks into the branch-e2e
workflow so they run alongside the other core Kubernetes e2e suites.
Both are gated by run_core_e2e and included in the Core E2E result
gate.
Signed-off-by: Derek Carr <decarr@redhat.com>
* test(k8s): add e2e tests for workspace namespace modes
Add 7 new e2e tests covering workspace namespace lifecycle, TLS secret
copying, ownership conflict detection, DNS-1123 validation, operator
namespace preservation, and dynamic label watcher behavior. Fix async
sandbox deletion race condition in existing tests by polling sandbox
list instead of asserting immediately after delete.
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(k8s): grant secrets/patch unconditionally and backfill gateway-id labels
Address two review findings:
1. RBAC: server-side apply (PATCH) is used for TLS secret sync in
multi-namespace modes, but the ClusterRole only granted patch when
the kubernetes-secrets credential driver was enabled. Grant patch
unconditionally for non-shared modes since TLS sync always needs it;
keep delete gated on the credential driver.
2. Upgrade safety: the new gateway-id label selector would orphan
legacy Sandbox CRs that predate its introduction. Add a startup
backfill in shared mode that patches any managed Sandbox CR missing
the gateway-id label before the driver begins serving requests.
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(k8s): address workspace namespace review findings
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(k8s): address follow-up review findings
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(k8s): preserve workspace lookup after rebase
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(helm): allow managed secret creation
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(k8s): stop pods in workspace namespace
Signed-off-by: Derek Carr <decarr@redhat.com>
* test(k8s): scope pod deletion check to v1alpha1
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(k8s): address workspace namespace review findings
Signed-off-by: Derek Carr <decarr@redhat.com>
---------
Signed-off-by: Derek Carr <decarr@redhat.com>
* feat(sdk): add TypeScript SDK (@nvidia/openshell-sdk)
First native, per-language SDK for the OpenShell gateway: a thin, idiomatic
TypeScript client over proto-generated gRPC stubs (connect-es), no FFI. Covers
the v0.1 surface — sandbox lifecycle (create/get/list/delete + waitReady/
waitDeleted), health, and streamed exec.
- sdk/typescript/: package, client/transport/errors, protoc + protoc-gen-es
codegen (gen/ gitignored, absorbed into dist/ at build), committed lockfile.
- tasks/typescript.toml: sdk:ts install/proto/typecheck/build/ci/publish;
sdk:ts:typecheck wired into `check`; sdk-typescript job in branch-checks
(typecheck, build, and a --dry-run publish that validates the release path).
- Enforce SPDX headers on .ts/.tsx/.mts/.cts (skip node_modules and gen/);
back-fill docs/_components/jsx.d.ts and fern/components/CustomFooter.tsx.
- release.py gains an npm version format; release-tag.yml publishes to
GitHub Packages on tag, stamping the version (0.0.0 placeholder in git);
prerelease builds publish under the `next` dist-tag, not `latest`.
Ships as @nvidia/openshell-sdk on GitHub Packages pre-GA; public npm
(@openshell/sdk) follows at GA with an unchanged public API.
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* chore(sdk): adopt TypeScript 6, tidy @types/node range
- typescript ^5.7.2 -> ^6.0.3 (6.0 is now `latest`; the old caret capped at 5.x)
- @types/node ^24.0.0 -> ^24 (same range, tidier)
No source changes; codegen, typecheck, and build pass on 6.0.3. Verified the
emitted d.ts still type-check for downstream consumers on TypeScript 5.0.4
through 5.9.3, so this does not raise the SDK's consumer TS floor.
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* refactor(sdk): group operations under a composable SandboxClient
Reshape the client from flat methods (createSandbox, listSandboxes, exec) to a
scoped SandboxClient reached as `client.sandbox.create/get/list/delete/exec`
(+ waitReady/waitDeleted), mirroring the CLI's noun-verb model and the Python
SDK's SandboxClient.
SandboxClient is also usable standalone via SandboxClient.connect();
OpenShellClient composes it over a single shared transport, so future
service/provider clients reuse one connection. health() stays top-level as a
gateway call. No behavior change; types are unchanged.
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* chore(sdk): generate TypeScript SDK stubs with buf
Replace the protoc gen.sh with `buf generate` + buf.gen.yaml. `buf`
(@bufbuild/buf) is a package devDependency and self-compiles the protos, so
the TS SDK no longer depends on the mise-pinned protoc; it drives the same
connect-es plugin. Generation stays limited to the client-surface closure
(openshell/sandbox/datamodel) via the input paths.
Output is byte-identical to the previous protoc + protoc-gen-es pipeline. Lays
the groundwork for a shared buf.yaml (lint/breaking/LSP) as a follow-up.
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* build(proto): add repo-level buf module with lint
Declare proto/ as a single buf v2 module in a root buf.yaml so buf
generate, lint, breaking, and the editor LSP resolve imports the same
way. Lint uses STANDARD with six documented exceptions for deviations
the current protos intentionally make: the flat proto/ layout with
nested packages (DIRECTORY_SAME_PACKAGE, PACKAGE_DIRECTORY_MATCH) and
the established API shape with unsuffixed services and reused
request/response messages (RPC_REQUEST_RESPONSE_UNIQUE,
RPC_REQUEST_STANDARD_NAME, RPC_RESPONSE_STANDARD_NAME, SERVICE_SUFFIX).
Every other STANDARD rule now enforces on future protos. Breaking uses
FILE.
Code generation stays package-scoped in sdk/typescript/buf.gen.yaml
since it binds to that package's connect-es plugin and output dir;
its inputs are unchanged and regeneration is byte-identical.
Wire the check in via a proto:lint mise task that runs buf from the
SDK devDependencies. It is a dependency of both sdk:ts:ci (so the
TypeScript SDK CI job enforces it) and the top-level lint aggregate
(so local pre-commit covers it).
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* chore(sdk): publish as unscoped openshell-sdk on public npm
Rename the package from @nvidia/openshell-sdk to the unscoped
openshell-sdk and target public npm (registry.npmjs.org) instead of
GitHub Packages. GitHub Packages requires a scope matching the owning
org, and the @openshell scope is blocked by an unrelated existing
package, so an unscoped name on public npm is the lowest-friction
distribution path and needs no org approval.
Rework the release-tag publish job to auth against registry.npmjs.org
with NPM_TOKEN (the job now only needs packages: read to pull the CI
image). Update the README install instructions and usage imports.
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* chore(sdk): publish @nvidia/openshell-sdk to GitHub Packages
Revert the unscoped-name switch. GitHub Packages only accepts scoped
names matching the owning org, so shipping there first (which needs no
external npm org or NPM_TOKEN, just the repo's GITHUB_TOKEN) requires
the @nvidia scope. Keeping the @nvidia/openshell-sdk name also lets a
later public-npm release use the same install specifier, so adding
public npm becomes a second publish step rather than a rename.
Restore the GitHub Packages publish auth in the release-tag job and the
scoped install instructions in the README (keeping the buf codegen
note).
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* feat(sdk-ts): add streaming exec, forward, ssh, provider, and config methods
Grow SandboxClient to the surface the first two consumers need. execStream
yields stdout/stderr chunks as they arrive and exec now drains it, keeping its
buffered ExecResult and signature unchanged. execInteractive is the TTY + stdin
transport primitive (start-first framing, output/write/resize/close/done, no
terminal glue). forward binds a local TCP listener that tunnels each accepted
connection into the sandbox for the process lifetime, minting and revoking a
per-socket SSH session token around a forwardTcp bidi. Adds createSshSession /
revokeSshSession, attach/detach/listProviders, and getConfig / setPolicy /
setSetting (sandbox-scoped, network-policy-only, with an optional wait poll).
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* build(sdk-ts): add Biome and Vitest tooling
The TypeScript SDK had no formatter or linter and no test runner. Add Biome
(format + lint, generated src/gen excluded) enforcing 2-space indent, single
quotes, semicolons, and a 120-column width, and reformat the existing
hand-written sources accordingly. Add Vitest for unit tests. Wire sdk:ts:format,
sdk:ts:lint, and sdk:ts:test mise tasks into the fmt/lint aggregates, the root
test suite, and sdk:ts:ci so they run in CI.
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* test(sdk-ts): cover the sandbox surface with in-memory transport tests
Exercise SandboxClient against an in-memory OpenShell service built with
createRouterTransport: request assembly and id resolution, u64/int64 rendered as
strings, enum lowercasing, fromConnect code mapping, the exec/execStream drain
plus a backward-compat check on exec, execInteractive start-first ordering and
done resolution, and a forward() byte relay against a loopback echo with close()
teardown.
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* docs(sdk-ts): document the new surface and connect/upload/download boundaries
Document execStream, execInteractive, forward, ssh sessions, providers, and
config/policy in the SDK README, and record the intentional boundaries:
interactive connect / PTY ownership, upload/download (no file-transfer RPC), and
detached forwards stay out of scope. Note the Biome/Vitest dev commands.
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* feat(sdk-ts): support mTLS client authentication
Add clientCert and clientKey to ConnectOptions so the SDK can
authenticate to the default local gateway, which uses mTLS user
authentication. Without a client certificate and key the SDK could
verify the server but never authenticate the caller, so it could not
connect to the standard Docker, VM, Homebrew, or Linux-package gateway.
Validate the pair as both-or-neither and pass cert and key through to
the Node TLS options for https gateways. The h2c path is unchanged.
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* chore(sdk-ts): drop the demo script and its tsx dependency
Remove src/demo.ts, the demo npm script, the tsx devDependency, and the
tsconfig build exclude for the demo. The demo was never part of the
published package, and dropping it also removes the only place that
logged part of an SSH session token.
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* fix(sdk-ts)!: harden exec streaming, waits, SSH, and forwarding
Address review feedback on the sandbox surface.
- Make the streamed command exit code observable from idiomatic
for-await: the terminal exit is now an in-band ExecStreamEvent
({ type: 'exit', exitCode }) rather than the async generator return
value, which for-await discards. A stream that ends without an exit
event now throws instead of reporting success.
- Bound waitReady, waitDeleted, and the setPolicy wait by their timeout:
each poll RPC carries a per-iteration deadline and the waits accept an
AbortSignal, so a stalled call can no longer leave a wait pending
forever. Add waitTimeoutSecs to SetPolicyOptions.
- Validate the CreateSshSession response against the proto charset and
range contract before returning it or using its token, since the
values feed an OpenSSH ProxyCommand.
- Respect socket backpressure when relaying forwarded responses: pause
reading the gRPC stream when the local socket buffer is full and
resume on drain so memory stays bounded.
- Expose create-time sandbox policy: add policy and an advanced rawSpec
passthrough to SandboxSpec so the safety boundary is expressible at
creation and new spec fields do not require an SDK change.
BREAKING CHANGE: execStream and the interactive exec output now yield a
terminal { type: 'exit', exitCode } event; consumers iterating the
stream must handle that arm. The exit code is no longer the async
generator return value.
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* feat(sdk-ts): export error contract, enum unions, and caller cancellation
Address Tier-1 review feedback on the TypeScript SDK public surface (PR #2122).
- Errors: export SdkError and SdkErrorCode so callers can use instanceof and
exhaustively switch on .code. fromConnect preserves the originating
ConnectError as .cause and its status as .connectCode, maps Aborted to a new
'aborted' code for optimistic-concurrency conflicts, and maps Canceled and
DeadlineExceeded to 'canceled'. errorCode() behavior is unchanged.
- Enums: replace the string-typed phase, status, scope, and policySource fields
with lowercase literal unions (SandboxPhaseName, HealthStatus,
SettingScopeName, PolicySourceName) backed by exhaustive Record maps. The
unions are a hand-maintained mirror of the generated proto enums; a new drift
test pins each literal to its generated member name.
- Cancellation: accept an optional AbortSignal on exec, execInteractive, and
forward, threaded into both sandbox resolution and the streaming RPC. forward
tears down its local listener on abort.
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* feat(sdk-ts): add raw escape hatch for uncurated gateway RPCs
The curated sub-clients reduce proto messages to ergonomic subsets (for
example get() drops created_at_ms, the full spec, conditions, runtime
endpoints, and current_policy_version), and not every gateway RPC has a
typed helper yet. Rather than ship methods that exist but throw, expose a
generated client for the full surface.
OpenShellClient.raw and SandboxClient.raw are generated clients covering
every gateway RPC, returning the verbatim wire messages so proto
distinctions the curated types smooth over are preserved. .transport
exposes the shared connection for building extra clients over one socket.
Generated request/response types are published at the new
@nvidia/openshell-sdk/raw subpath. Curated methods stay the default;
raw is the always-available floor.
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* fix(sdk-ts): address review feedback on exec, forward, and auth transport
Settle exec's `done` promise before yielding the exit event so a consumer
that breaks on exit no longer leaves it pending forever, and give it a lone
rejection handler plus a finally-settle so a stream error or early abandon
can never surface as an unhandled rejection or a hang.
Attach an 'error' listener to each accepted forward socket synchronously,
before forwardConnection awaits CreateSshSession; a peer reset in that
window previously emitted an unhandled 'error' and crashed the process.
Reject ambiguous or unsafe transport configs at buildTransport: oidcToken
and edgeToken together (silently OIDC-only), and any auth token sent over
plaintext http:// to a non-loopback host unless allowInsecureAuth is set.
Wrap versionPin so a non-u64 expectedResourceVersion raises
SdkError('invalid_config') instead of a raw BigInt SyntaxError, and raise
the Node engine floor to >=20.3 for AbortSignal.any().
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
* fix(sdk-ts): address review feedback
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
* docs(sdk-ts): defer published sdk guide
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
---------
Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Co-authored-by: Drew Newberry <anewberry@nvidia.com>