Files
OpenShell/architecture/build.md
Max DubrinskyandDrew Newberry 35fb27ef14 feat(sdk): add TypeScript SDK (@nvidia/openshell-sdk) (#2122)
* 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>
2026-08-13 20:50:38 +00:00

15 KiB

Build

This page records the stable build, CI, docs, and release architecture. It is not a command reference. Contributor-facing workflow details live in CONTRIBUTING.md, CI.md, and published docs.

Artifacts

OpenShell builds these main artifacts:

Artifact Source
Gateway binary crates/openshell-server
CLI package and Python SDK python/openshell plus Rust binaries where packaged
TypeScript SDK package sdk/typescript
Gateway container image deploy/docker/Dockerfile.gateway
Supervisor container image deploy/docker/Dockerfile.supervisor
Helm chart deploy/helm/openshell
VM driver/runtime assets crates/openshell-driver-vm
Published docs site docs/ rendered by Fern config in fern/

Sandbox community images are built outside this repository.

Build Features

Anonymous telemetry emission is gated behind a default-on telemetry Cargo feature. It is defined in openshell-core (where the emission code, HTTP client, and endpoint live) and forwarded by the binary crates that emit or collect telemetry: openshell-server (gateway), openshell-sandbox (supervisor), and openshell-driver-vm. Every crate depends on openshell-core with default-features = false, so the binary crate's feature is the single switch that enables openshell-core/telemetry for its build graph. In-process drivers (docker, kubernetes, podman) inherit the gateway's setting through feature unification and carry no passthrough.

Building a binary with --no-default-features compiles out telemetry entirely: no endpoint, no telemetry HTTP client, and no emission code. With telemetry compiled out, telemetry::enabled() is always false and the emit_* helpers are no-ops, so the data-model types stay available and dependent crates compile unchanged. The runtime OPENSHELL_TELEMETRY_ENABLED switch remains the way to disable telemetry in a default (telemetry-enabled) build.

Supervisor upstream TLS root-store selection is controlled by the bundled-ca-roots Cargo feature (on by default). Default builds use Mozilla roots through webpki-roots plus locally-installed CAs from the system bundle. Building without bundled-ca-roots switches to the platform trust store via rustls-native-certs and excludes bundled Mozilla root crates such as webpki-roots and webpki-root-certs from the dependency graph. The system-ca-roots feature alias on openshell-sandbox includes all other defaults (currently telemetry) except bundled-ca-roots, so Linux distribution builds (e.g. RPM) can use --no-default-features --features system-ca-roots without manually re-adding unrelated defaults. Other Rustls clients use native roots directly because that already satisfies Linux distribution trust-store policy.

The workspace uses z3 versions whose z3-sys dependency keeps downloader HTTP/TLS support behind explicit build features, so default system-Z3 builds do not reintroduce bundled Mozilla roots. Release builds that need bundled Z3 continue to opt in with bundled-z3.

Linux Runtime Environments

OpenShell uses different Linux libc environments for different host artifacts. The standalone openshell CLI is built as a static musl binary so it can run on a wide range of Linux distributions without depending on the host's glibc. Host runtime binaries that use the GNU/Linux runtime environment are GNU-linked. openshell-gateway and openshell-driver-vm are built with a glibc 2.28 floor. The gateway bundles z3 into the release binary so Linux packages, standalone tarballs, and gateway images do not depend on distro-specific z3 shared-library SONAMEs.

The supervisor is the one binary whose libc is selectable, because it is the one binary executed inside a userland OpenShell does not control. SUPERVISOR_LIBC chooses between musl (default) and glibc-static. Both produce a fully static binary; the choice does not change the runtime layout or the supervisor image base. Static linkage is a hard requirement rather than a preference, so both variants are verified by tasks/scripts/verify-static-binary.sh, which fails the build on any PT_INTERP or DT_NEEDED entry.

The two variants differ only in build-time constraints:

musl (default) glibc-static
Cross-compiles yes, via cargo zigbuild no — must build natively per architecture
Host requirement zig + cargo-zigbuild glibc static libraries (glibc-static on Fedora/RHEL, libc6-dev on Debian/Ubuntu)
libc license MIT LGPL-2.1-or-later, statically linked

cargo zigbuild cannot produce the glibc-static variant: zig cc accepts -static for *-linux-gnu targets and emits a dynamically linked binary anyway. The staging script therefore refuses to cross-compile that variant instead of silently degrading linkage.

Selecting glibc-static statically links LGPL glibc into a redistributed binary, which carries relinking obligations that musl (MIT) does not. Treat the default as the shipping configuration unless that has been reviewed.

Container Builds

The Docker image pipeline is a two-step flow: build the Rust binary natively for the target architecture, then assemble the container image from the prebuilt binary. The gateway image is built from deploy/docker/Dockerfile.gateway and the supervisor image from deploy/docker/Dockerfile.supervisor. Neither Dockerfile compiles Rust — both copy a staged binary out of deploy/docker/.build/prebuilt-binaries/<arch>/ into the final image.

Binary staging is driven by tasks/scripts/stage-prebuilt-binaries.sh. Because staging cross-compiles on the host, it sources tasks/scripts/build-env.sh and raises the per-process open-file limit before invoking cargo zigbuild on macOS — the static musl link opens hundreds of .rlib files at once and would otherwise fail with ProcessFdQuotaExceeded under macOS's default soft limit of 256. The guard is a no-op on Linux and when cargo-zigbuild is absent. Gateway binaries use cargo zigbuild with GNU targets pinned to glibc 2.28, including native-architecture builds, so the gateway image, standalone tarballs, and Linux packages share the same host portability floor. The gateway build enables bundled-z3. Linux VM driver release artifacts use the same glibc floor so package-managed VM support does not raise the package runtime requirement. Gateway staging and release workflows set up the Zig C/C++ wrapper before bundled Z3 builds and verify the maximum referenced GLIBC_* symbol version before publishing or copying artifacts. Supervisor binaries are static in every configuration. The default musl variant uses cargo zigbuild when available, including native CPU architectures, so C dependencies are compiled for the musl target instead of the host GNU libc target. The glibc-static variant uses plain cargo build with +crt-static and requires a native per-architecture build. Local Docker image tasks infer the target architecture from DOCKER_PLATFORM when set. Otherwise, they require valid container engine host metadata and fail when the engine query is unavailable or reports an unsupported architecture, avoiding host-kernel fallbacks that can target the wrong architecture. CI invokes the same staging step via the rust-native-build.yml workflow (per-architecture, per-component) and uploads the result as an artifact that the image build job downloads back into the staging directory before running Buildx.

Runtime layout:

  • Gateway: gcr.io/distroless/cc-debian13:nonroot base, GNU-linked binary at /usr/local/bin/openshell-gateway, runs as UID/GID 1000:1000. Linux GNU gateway binaries must not reference GLIBC_* symbols newer than GLIBC_2.28; release workflows verify this before publishing artifacts. The gateway bundles z3, so the image does not need a distro-provided z3 runtime.
  • VM driver: host GNU-linked binary installed at /usr/libexec/openshell/openshell-driver-vm in Linux packages and published as a release artifact. Linux GNU VM driver binaries must not reference GLIBC_* symbols newer than GLIBC_2.28; release workflows verify this before publishing artifacts.
  • Supervisor: Alpine base with nftables, static binary at /openshell-sandbox (musl by default; see SUPERVISOR_LIBC above). Static linkage keeps the binary usable when the image is mounted/extracted into sandbox environments (Docker extraction, Podman image volumes, Kubernetes init-container copy-self), whose libc and glibc version are not known at build time, while nftables supports Kubernetes supervisor sidecar egress enforcement. The VM driver bundles its own supervisor build (tasks/scripts/vm/build-supervisor-bundle.sh) and does not read SUPERVISOR_LIBC.

Gateway image builds bake the corresponding supervisor image tag into the gateway binary so Docker sandboxes do not depend on :latest by default. The Helm chart omits the supervisor image from gateway configuration unless an operator supplies a repository or tag override, preserving that build-time pairing for Kubernetes sandboxes as well. Package formulas also pin Docker supervisor extraction to the matching release image tag so standalone gateway binaries do not infer image tags from package versions. The Homebrew service keeps gateway TLS under the Homebrew state directory but mirrors Docker sandbox client TLS into $HOME/.local/state/openshell/homebrew/tls at service start, because Docker Desktop bind mounts must use paths visible to the macOS user's shared home directory.

Local image work should use mise tasks rather than direct Docker commands so the same staging and tagging assumptions are used locally and in CI.

Container-engine selection is centralized in tasks/scripts/container-engine.sh. CONTAINER_ENGINE=docker|podman is the only explicit override. Docker- and Podman-backed e2e wrappers validate that override against their lane, set OPENSHELL_E2E_DRIVER, and reject the removed OPENSHELL_E2E_CONTAINER_ENGINE selector so build helpers and Rust e2e support containers use the same engine. When no explicit override is present, an e2e driver requirement wins, then a local-cluster requirement, then host auto-detection.

Local Kubernetes image workflows opt into cluster-aware selection with CONTAINER_ENGINE_TARGET=local-k8s-cluster. The hint is intentionally scoped to Skaffold-style push: false builds where the image must land in the engine backing the active local cluster: k3d-* contexts require Docker, kind-* contexts use KIND_EXPERIMENTAL_PROVIDER=docker|podman when set, and ambiguous or unknown contexts require an explicit CONTAINER_ENGINE. Other image builds do not infer from kube context.

Disposable Test Guests

The Nix test guest harness under nix/test-guest boots native-architecture cloud images through QEMU for package, release, and E2E validation. A prepared cache entry is captured after the exact ordered Ansible configuration list and before test-specific packages, copied binaries, forwarded ports, or commands.

Prepared disks are flattened, sanitized QCOW2 images. The local cache keeps them read-only and each test receives a fresh writable overlay and cloud-init identity. The optional shared cache stores the compressed standalone disk and its compatibility metadata as a custom OCI artifact. Normal test runs ensure the exact local entry exists, invoking the cache builder automatically on a miss before booting a disposable overlay. The separate cache app owns OCI pulls and explicit publication. OCI pulls require a trusted manifest digest and retain that provenance with the local entry; mutable tags are used only for explicit publication.

Python Wheel Packaging

The generated protobuf/gRPC stubs under python/openshell/_proto/ are gitignored build outputs of mise run python:proto. The task uses uv run --frozen to synchronize the current worktree's .venv from uv.lock before generation. maturin honors .gitignore when collecting python-source files, so native builds (Linux CI, local pip install .) would drop them and ship an unimportable wheel. pyproject.toml pins them back in with [tool.maturin].include globs. The release workflows install each Linux wheel in a clean image and import openshell.sandbox as a smoke check.

TypeScript SDK Packaging

The native TypeScript SDK in sdk/typescript uses Connect over the generated OpenShell protobuf surface. sdk/typescript/buf.gen.yaml selects the client proto closure, and mise run sdk:ts:proto generates gitignored sources under src/gen. TypeScript compilation includes those sources in dist, so package consumers do not run code generation.

Branch checks run mise run sdk:ts:ci, enforce an 80% line-coverage floor, and exercise version stamping plus npm publish --dry-run. Tagged releases publish @nvidia/openshell-sdk to GitHub Packages. The repository keeps package version 0.0.0; the release task derives and temporarily stamps the npm version from the release tag.

CI and E2E

Required checks run on GitHub Actions. Workflows that use NVIDIA self-hosted runners trigger from copy-pr-bot mirror branches, so trusted PRs are mirrored into pull-request/<N> branches before those workflows run. main also uses GitHub merge queue so the final queued integration commit is validated before it merges.

The high-level CI model:

  1. PR-context gate jobs publish required statuses for the PR head commit.
  2. Standard branch checks run from trusted mirror branches.
  3. Label-gated Docker, Podman, VM, GPU, and Kubernetes E2E checks run from trusted mirror branches.
  4. Merge-group checks run against GitHub's temporary queue branch for the final integration state.
  5. Gate jobs verify that the mirror branch matches the PR head, or that the merge-group workflow ran for the queued SHA, and that the expected non-gate workflow actually ran.
  6. Release workflows rebuild and publish binaries, wheels, images, and docs.

Repository CI keeps telemetry compiled into release-parity artifacts but disables emission for Rust tests, E2E runs, and release canaries. This prevents synthetic activity from contributing to product usage metrics.

See CI.md for the contributor workflow, labels, and maintainer merge-queue workflow.

Docs Site

Published docs live in docs/. Navigation lives in docs/index.yml. Fern site configuration, components, theme assets, and publish settings live in fern/.

Use mise run docs for strict validation and mise run docs:serve for local preview. PR previews are produced by .github/workflows/branch-docs.yml when Fern credentials are available. Production docs publish from the release tag workflow.

Validation Expectations

  • Run mise run pre-commit before committing.
  • Run mise run test after code changes.
  • Run mise run e2e for sandbox, policy, driver, or deployment changes when the affected runtime can be exercised.
  • Run mise run ci before opening a PR when practical.
  • Run mise run docs when docs/ or fern/ changes.

Architecture-only changes should still check links and references because this directory is used by agents during implementation and review.