diff --git a/.agents/skills/launch-openshell-gator/SKILL.md b/.agents/skills/launch-openshell-gator/SKILL.md index 2f9680d411..566dea13b2 100644 --- a/.agents/skills/launch-openshell-gator/SKILL.md +++ b/.agents/skills/launch-openshell-gator/SKILL.md @@ -80,6 +80,8 @@ jq -e '.tokens.access_token and .tokens.refresh_token and .tokens.account_id' "$ If this fails, run the local Codex login flow outside the gator launch. If Codex was recently reauthenticated and gateway refresh fails later, relaunch with `--reset-refresh` once. +The launcher passes the non-secret host account ID as literal `CODEX_ACCOUNT_ID` for Codex's local workspace routing. The harness must not use the opaque `CODEX_AUTH_ACCOUNT_ID` credential placeholder for that comparison. Access tokens remain placeholders, and refresh material remains gateway-only. + ### Step 5: Verify Gateway Is Registered And Alive Use the target gateway from the operator request or current session context. Do not assume a gateway name. If the operator did not specify one, list registered gateways and ask before launching when the correct target is ambiguous. diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index 85097eea56..d46f3fb31f 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -1,8 +1,6 @@ name: Branch E2E Checks on: - merge_group: - types: [checks_requested] push: branches: - "pull-request/[0-9]+" @@ -50,15 +48,6 @@ jobs: run_kubernetes_ha_e2e="$(jq -r 'index("test:e2e-kubernetes") != null' <<< "$LABELS_JSON")" run_kubernetes_credential_drivers_e2e="$(jq -r 'index("test:e2e-kubernetes") != null' <<< "$LABELS_JSON")" ;; - merge_group) - # Merge groups have no PR labels. When GPU E2E is required as documented - # in CI.md, skipping it leaves the gate pending until the queue times out - # and ejects the PR. HA stays off until stable. - run_core_e2e=true - run_gpu_e2e=true - run_kubernetes_ha_e2e=false - run_kubernetes_credential_drivers_e2e=false - ;; *) run_core_e2e=true run_gpu_e2e=true @@ -82,7 +71,7 @@ jobs: version: needs: [pr_metadata] - if: needs.pr_metadata.outputs.should_run == 'true' + if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_any_e2e == 'true' permissions: contents: read runs-on: ubuntu-latest @@ -228,7 +217,7 @@ jobs: build-rpm: name: Build RPM packages needs: [pr_metadata, version, build-binaries] - if: needs.pr_metadata.outputs.should_run == 'true' + if: needs.pr_metadata.outputs.run_integration == 'true' permissions: actions: read contents: read diff --git a/.github/workflows/codex-security.yml b/.github/workflows/codex-security.yml index 348817ba89..5da0994698 100644 --- a/.github/workflows/codex-security.yml +++ b/.github/workflows/codex-security.yml @@ -76,9 +76,9 @@ concurrency: env: CODEX_SECURITY_MAX_CONCURRENT_THREADS: "8" - CODEX_SECURITY_REASONING_EFFORT: medium + CODEX_SECURITY_REASONING_EFFORT: high NVIDIA_INFERENCE_BASE_URL: https://inference-api.nvidia.com/v1 - NVIDIA_INFERENCE_MODEL: openai/openai/gpt-5.6-sol + NVIDIA_INFERENCE_MODEL: openai/openai/gpt-6.1-sol jobs: analyze: diff --git a/.github/workflows/required-ci-gates.yml b/.github/workflows/required-ci-gates.yml index accb36d7a7..2310d27efe 100644 --- a/.github/workflows/required-ci-gates.yml +++ b/.github/workflows/required-ci-gates.yml @@ -281,6 +281,9 @@ jobs: # evaluate_workflow's verify_mirror would pend these gates forever downstream, # since nothing creates that mirror branch here. Mark them passing directly. post_status "OpenShell / Branch Checks" success "Disabled in downstream fork (job requires runners unavailable downstream)" "$STATUS_TARGET_URL" - evaluate_workflow "OpenShell / E2E" "branch-e2e.yml" "Branch E2E Checks" "test:e2e" "Core E2E result" - evaluate_workflow "OpenShell / GPU E2E" "branch-e2e.yml" "Branch E2E Checks" "test:e2e-gpu" "GPU E2E result" + # E2E is opt-in on PRs and does not run for merge groups. + if [ "$CONTEXT_KIND" != "merge_group" ]; then + evaluate_workflow "OpenShell / E2E" "branch-e2e.yml" "Branch E2E Checks" "test:e2e" "Core E2E result" + evaluate_workflow "OpenShell / GPU E2E" "branch-e2e.yml" "Branch E2E Checks" "test:e2e-gpu" "GPU E2E result" + fi post_status "OpenShell / Helm Lint" success "Disabled in downstream fork (job requires runners unavailable downstream)" "$STATUS_TARGET_URL" diff --git a/.tekton/odh-openshell-e2e-pull-request.yaml b/.tekton/odh-openshell-e2e-pull-request.yaml new file mode 100644 index 0000000000..7b1f6a586e --- /dev/null +++ b/.tekton/odh-openshell-e2e-pull-request.yaml @@ -0,0 +1,66 @@ +apiVersion: tekton.dev/v1 +kind: PipelineRun +metadata: + annotations: + build.appstudio.openshift.io/repo: https://github.com/opendatahub-io/openshell?rev={{revision}} + build.appstudio.redhat.com/commit_sha: '{{revision}}' + build.appstudio.redhat.com/target_branch: '{{target_branch}}' + pipelinesascode.tekton.dev/cancel-in-progress: "false" + pipelinesascode.tekton.dev/max-keep-runs: "3" + pipelinesascode.tekton.dev/on-cel-expression: event == "pull_request" && target_branch + == "main" + creationTimestamp: null + labels: + appstudio.openshift.io/application: opendatahub-builds + appstudio.openshift.io/component: odh-openshell-e2e-ci + pipelines.appstudio.openshift.io/type: build + name: odh-openshell-e2e-on-pull-request + namespace: open-data-hub-tenant +spec: + params: + - name: git-url + value: '{{source_url}}' + - name: revision + value: '{{revision}}' + - name: output-image + value: quay.io/opendatahub/odh-openshell-e2e:odh-pr + - name: dockerfile + value: deploy/docker/Dockerfile.konflux.e2e-odh + - name: path-context + value: . + - name: hermetic + value: "true" + - name: build-platforms + value: + - linux-m2xlarge/amd64 + - linux-m2xlarge/arm64 + - name: prefetch-input + value: | + [ + {"type": "cargo", "path": "."}, + {"type": "cargo", "path": "e2e/rust"}, + {"type": "rpm", "path": "deploy/konflux/e2e-odh"}, + {"type": "generic", "path": "deploy/konflux/e2e-odh", "lockfile": "generic-fetcher.yaml"} + ] + #add these additional params + - name: additional-tags + value: + - 'odh-pr-{{revision}}' + - name: pipeline-type + value: pull-request + pipelineRef: + resolver: git + params: + - name: url + value: https://github.com/opendatahub-io/odh-konflux-central.git + - name: revision + value: main + - name: pathInRepo + value: pipeline/multi-arch-container-build.yaml + taskRunTemplate: + serviceAccountName: build-pipeline-odh-openshell-e2e-ci + workspaces: + - name: git-auth + secret: + secretName: '{{ git_auth_secret }}' +status: {} diff --git a/.tekton/odh-openshell-e2e-push.yaml b/.tekton/odh-openshell-e2e-push.yaml new file mode 100644 index 0000000000..3bcfb7c17f --- /dev/null +++ b/.tekton/odh-openshell-e2e-push.yaml @@ -0,0 +1,61 @@ +apiVersion: tekton.dev/v1 +kind: PipelineRun +# +metadata: + annotations: + build.appstudio.openshift.io/repo: https://github.com/opendatahub-io/openshell?rev={{revision}} + build.appstudio.redhat.com/commit_sha: '{{revision}}' + build.appstudio.redhat.com/target_branch: '{{target_branch}}' + pipelinesascode.tekton.dev/cancel-in-progress: "false" + pipelinesascode.tekton.dev/max-keep-runs: "3" + pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch + == "main" + creationTimestamp: null + labels: + appstudio.openshift.io/application: opendatahub-builds + appstudio.openshift.io/component: odh-openshell-e2e-ci + pipelines.appstudio.openshift.io/type: build + name: odh-openshell-e2e-on-push + namespace: open-data-hub-tenant +spec: + params: + - name: git-url + value: '{{source_url}}' + - name: revision + value: '{{revision}}' + - name: output-image + value: quay.io/opendatahub/odh-openshell-e2e:odh-stable + - name: dockerfile + value: deploy/docker/Dockerfile.konflux.e2e-odh + - name: path-context + value: . + - name: hermetic + value: "true" + - name: build-platforms + value: + - linux-m2xlarge/amd64 + - linux-m2xlarge/arm64 + - name: prefetch-input + value: | + [ + {"type": "cargo", "path": "."}, + {"type": "cargo", "path": "e2e/rust"}, + {"type": "rpm", "path": "deploy/konflux/e2e-odh"}, + {"type": "generic", "path": "deploy/konflux/e2e-odh", "lockfile": "generic-fetcher.yaml"} + ] + pipelineRef: + resolver: git + params: + - name: url + value: https://github.com/opendatahub-io/odh-konflux-central.git + - name: revision + value: main + - name: pathInRepo + value: pipeline/multi-arch-container-build.yaml + taskRunTemplate: + serviceAccountName: build-pipeline-odh-openshell-e2e-ci + workspaces: + - name: git-auth + secret: + secretName: '{{ git_auth_secret }}' +status: {} diff --git a/.tekton/odh-openshell-sandbox-openclaw-pull-request.yaml b/.tekton/odh-openshell-sandbox-openclaw-pull-request.yaml new file mode 100644 index 0000000000..eb8a3b8771 --- /dev/null +++ b/.tekton/odh-openshell-sandbox-openclaw-pull-request.yaml @@ -0,0 +1,64 @@ +apiVersion: tekton.dev/v1 +kind: PipelineRun +metadata: + annotations: + build.appstudio.openshift.io/repo: https://github.com/opendatahub-io/openshell?rev={{revision}} + build.appstudio.redhat.com/commit_sha: '{{revision}}' + build.appstudio.redhat.com/target_branch: '{{target_branch}}' + pipelinesascode.tekton.dev/cancel-in-progress: "false" + pipelinesascode.tekton.dev/max-keep-runs: "3" + pipelinesascode.tekton.dev/on-cel-expression: event == "pull_request" && target_branch + == "main" + creationTimestamp: null + labels: + appstudio.openshift.io/application: opendatahub-builds + appstudio.openshift.io/component: odh-openshell-sandbox-openclaw-ci + pipelines.appstudio.openshift.io/type: build + name: odh-openshell-sandbox-openclaw-on-pull-request + namespace: open-data-hub-tenant +spec: + params: + - name: git-url + value: '{{source_url}}' + - name: revision + value: '{{revision}}' + - name: output-image + value: quay.io/opendatahub/odh-openshell-sandbox-openclaw:odh-pr + - name: dockerfile + value: deploy/docker/Dockerfile.konflux.openclaw + - name: path-context + value: . + - name: hermetic + value: "true" + - name: build-platforms + value: + - linux/x86_64 + - linux/arm64 + - name: prefetch-input + value: | + [ + {"type": "npm", "path": "deploy/konflux/openclaw"}, + {"type": "rpm", "path": "deploy/konflux/openclaw"} + ] + #add these additional params + - name: additional-tags + value: + - 'odh-pr-{{revision}}' + - name: pipeline-type + value: pull-request + pipelineRef: + resolver: git + params: + - name: url + value: https://github.com/opendatahub-io/odh-konflux-central.git + - name: revision + value: main + - name: pathInRepo + value: pipeline/multi-arch-container-build.yaml + taskRunTemplate: + serviceAccountName: build-pipeline-odh-openshell-sandbox-openclaw-ci + workspaces: + - name: git-auth + secret: + secretName: '{{ git_auth_secret }}' +status: {} diff --git a/.tekton/odh-openshell-sandbox-openclaw-push.yaml b/.tekton/odh-openshell-sandbox-openclaw-push.yaml new file mode 100644 index 0000000000..12b673c8d7 --- /dev/null +++ b/.tekton/odh-openshell-sandbox-openclaw-push.yaml @@ -0,0 +1,59 @@ +apiVersion: tekton.dev/v1 +kind: PipelineRun +# +metadata: + annotations: + build.appstudio.openshift.io/repo: https://github.com/opendatahub-io/openshell?rev={{revision}} + build.appstudio.redhat.com/commit_sha: '{{revision}}' + build.appstudio.redhat.com/target_branch: '{{target_branch}}' + pipelinesascode.tekton.dev/cancel-in-progress: "false" + pipelinesascode.tekton.dev/max-keep-runs: "3" + pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch + == "main" + creationTimestamp: null + labels: + appstudio.openshift.io/application: opendatahub-builds + appstudio.openshift.io/component: odh-openshell-sandbox-openclaw-ci + pipelines.appstudio.openshift.io/type: build + name: odh-openshell-sandbox-openclaw-on-push + namespace: open-data-hub-tenant +spec: + params: + - name: git-url + value: '{{source_url}}' + - name: revision + value: '{{revision}}' + - name: output-image + value: quay.io/opendatahub/odh-openshell-sandbox-openclaw:odh-stable + - name: dockerfile + value: deploy/docker/Dockerfile.konflux.openclaw + - name: path-context + value: . + - name: hermetic + value: "true" + - name: build-platforms + value: + - linux/x86_64 + - linux/arm64 + - name: prefetch-input + value: | + [ + {"type": "npm", "path": "deploy/konflux/openclaw"}, + {"type": "rpm", "path": "deploy/konflux/openclaw"} + ] + pipelineRef: + resolver: git + params: + - name: url + value: https://github.com/opendatahub-io/odh-konflux-central.git + - name: revision + value: main + - name: pathInRepo + value: pipeline/multi-arch-container-build.yaml + taskRunTemplate: + serviceAccountName: build-pipeline-odh-openshell-sandbox-openclaw-ci + workspaces: + - name: git-auth + secret: + secretName: '{{ git_auth_secret }}' +status: {} diff --git a/CI.md b/CI.md index 1a1a74280f..a433754151 100644 --- a/CI.md +++ b/CI.md @@ -72,8 +72,8 @@ Windows checks are not required for merging and do not run in merge queues. Main and manual runs also build release binaries, with `continue-on-error: true` so Windows failures do not fail the workflow. -Every approved `Branch E2E Checks` run builds the RPM packages, including -runs without optional E2E labels. Core integration qualification builds and installs +`Branch E2E Checks` builds binaries and images only when an E2E suite is +selected. DEB and RPM packages build only when integration qualification is selected. Core integration qualification builds and installs the DEB on Ubuntu with Docker and installs the CLI and gateway RPMs on Fedora with rootful and rootless Podman. These lanes run conformance using the matching runtime images. Release Dev and Release Tag use the same package installers. @@ -433,8 +433,6 @@ Important: if a PR requires manual admission, every new commit needs another `/o GitHub merge queue is required for `main`. Repository administrators must enable **Require merge queue** in the branch ruleset for `main` and keep these required status contexts aligned with the PR gates: - `OpenShell / Branch Checks` -- `OpenShell / E2E` -- `OpenShell / GPU E2E` - `OpenShell / Helm Lint` - `OpenShell / Trivy Changes` @@ -445,7 +443,7 @@ its own stable result status. Merge-group runs use the `merge_group` event. The event is distinct from `pull_request` and `push`, and GitHub will not report required checks for queued PRs unless the workflows include it. In this repository: - `Branch Checks` runs the standard non-E2E gates on the merge-group SHA. -- `Branch E2E Checks` runs core E2E and GPU E2E for merge groups. Kubernetes HA E2E remains optional and label-driven on PRs. +- `Branch E2E Checks` does not run for merge groups. E2E suites remain opt-in on PRs. - `Helm Lint` runs for merge groups without the PR diff optimization, because the merge-group branch is the final integration state. - `Trivy Changes` compares the merge-group configuration with its base and rejects new High or Critical findings. - `Required CI Gates` posts the same `OpenShell / ...` statuses to the merge-group SHA and does not require a `pull-request/` mirror for merge-group events. @@ -471,7 +469,7 @@ The bot's full administrator documentation is internal to NVIDIA. The only comma | File | Role | |---|---| | `.github/workflows/branch-checks.yml` | Required non-E2E checks. Triggers on `push: pull-request/[0-9]+` for PR mirrors and `merge_group` for queued merges. | -| `.github/workflows/branch-e2e.yml` | Standard, GPU, Kubernetes HA, and Kubernetes credential-driver E2E. PR mirror pushes use `test:e2e`, `test:e2e-gpu`, and `test:e2e-kubernetes` labels; merge groups run core and GPU E2E. | +| `.github/workflows/branch-e2e.yml` | Standard, GPU, Kubernetes HA, and Kubernetes credential-driver E2E. PR mirror pushes use `test:e2e`, `test:e2e-gpu`, and `test:e2e-kubernetes` labels; merge groups do not run E2E. | | `.github/workflows/build-binaries.yml`, `build-vm-driver.yml` | Shared binary matrices used by branch and release workflows. The VM driver remains separate because its build consumes the runtime binaries. | | `.github/workflows/build-images.yml` | Builds and pushes multi-platform images, then uploads the same OCI images as workflow artifacts. | | `.github/workflows/package-release-binaries.yml` | Packages raw build artifacts into release tarballs without rebuilding them. | @@ -507,11 +505,14 @@ These workflows run after merge to publish dev/tagged artifacts and verify them. Require these statuses in the branch ruleset for PR and merge-queue CI: - `OpenShell / Branch Checks` -- `OpenShell / E2E` -- `OpenShell / GPU E2E` - `OpenShell / Helm Lint` - `OpenShell / Trivy Changes` +The following statuses are opt-in and controlled by labels: + +- `OpenShell / E2E`: `test:e2e` +- `OpenShell / GPU E2E`: `test:e2e-gpu` + For mirror-based workflows, require the statuses published by `Required CI Gates`, not their underlying jobs. `OpenShell / Trivy Changes` is the stable result job of the direct pull-request workflow. Together these diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7da8be04b3..7ea44911a0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -291,6 +291,22 @@ each worktree. Cache reuse therefore depends on the compiler inputs: outputs that embed absolute paths, including Rust dependencies in some builds, can still miss across worktrees. +## Container Image Base Overrides + +The gateway and supervisor Dockerfiles accept `GATEWAY_BASE_IMAGE` and +`SUPERVISOR_BASE_IMAGE` build arguments. Omitting them keeps the pinned +distroless defaults. To build with your own bases, pass the argument through +the corresponding task: + +```shell +mise run build:docker:gateway -- --build-arg GATEWAY_BASE_IMAGE=registry.example.com/gateway-base:tag +mise run build:docker:supervisor -- --build-arg SUPERVISOR_BASE_IMAGE=registry.example.com/supervisor-base:tag +``` + +Choose bases that provide the GNU runtime libraries required by the binaries +and CA certificates for the target architecture. Validate the resulting images +with your deployment. The sandbox image uses `scratch` and has no base override. + ## Main Tasks These are the primary `mise` tasks for day-to-day development: diff --git a/Cargo.lock b/Cargo.lock index 3a34f0cefa..0e89a5a20a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5369,18 +5369,19 @@ dependencies = [ [[package]] name = "pageant" -version = "0.2.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b537f975f6d8dcf48db368d7ec209d583b015713b5df0f5d92d2631e4ff5595" +checksum = "6d8eab09a361a4afe0b1668be978cd97e4f052e927a92b0b608cf902965d49ce" dependencies = [ + "base16ct 1.0.0", "byteorder", "bytes", "delegate", "futures", "log", - "rand 0.8.6", - "sha2 0.10.9", - "thiserror 1.0.69", + "rand 0.10.2", + "sha2 0.11.0", + "thiserror 2.0.20", "tokio", "windows", "windows-strings", diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index ba7d2d7dfa..edcefad33c 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -4434,7 +4434,10 @@ impl ComputeRuntime { SandboxPhase::try_from(sandbox.phase()).unwrap_or(SandboxPhase::Unknown) }); - if existing_phase != SandboxPhase::Starting { + let ready_container_exit = existing_phase == SandboxPhase::Ready + && (driver_snapshot_confirms_stopped(&incoming) + || driver_snapshot_reports_runtime_restart(&incoming)); + if existing_phase != SandboxPhase::Starting && !ready_container_exit { return self.apply_sandbox_update_locked(incoming, existing).await; } @@ -4442,10 +4445,12 @@ impl ComputeRuntime { // durable phase to Starting. In particular, an old-generation Ready // event followed by its terminal event can otherwise promote and then // stop the new generation before the replacement supervisor connects. + // The replacement may already be Ready when the old exit arrives, so + // terminal container snapshots need the same check after readiness. // Release the global watch lock, wait for that lifecycle operation, // and then reread both the driver and store before applying an // authoritative observation. Taking the per-sandbox gate only for - // this ambiguous phase avoids delaying unrelated watch events behind + // these ambiguous snapshots avoids delaying unrelated watch events behind // slow lifecycle operations. let existing_name = existing_sandbox.as_ref().map_or_else( || incoming.name.clone(), @@ -4475,7 +4480,7 @@ impl ComputeRuntime { { warn!( sandbox_id = %incoming.id, - "Could not validate driver snapshot during sandbox start; retaining current sandbox state" + "Could not validate driver snapshot against current runtime; retaining current sandbox state" ); return Ok(()); } @@ -11604,6 +11609,65 @@ mod tests { } } + #[tokio::test] + async fn container_exit_after_replacement_ready_is_revalidated() { + for (reason, live_exited) in [ + ("ContainerExited", false), + ("ContainerExited", true), + ("ContainerStopped", false), + ("ContainerStopped", true), + ("ContainerRuntimeRestart", false), + ("ContainerRuntimeRestart", true), + ] { + let driver = ControlledDriver::new(); + let mut sandbox = + sandbox_record("sb-restarted", "sandbox-restarted", SandboxPhase::Starting); + sandbox.status.as_mut().unwrap().restart_count = 1; + sandbox.status.as_mut().unwrap().exit_code = Some(17); + sandbox.status.as_mut().unwrap().main_process_instance_id = "previous".to_string(); + let mut exited = ready_driver_sandbox(sandbox.object_id(), sandbox.object_name()); + exited.status = Some(make_driver_status(make_driver_condition( + reason, + "container exited", + ))); + let live = if live_exited { + exited.clone() + } else { + ready_driver_sandbox(sandbox.object_id(), sandbox.object_name()) + }; + driver.set_get_outcome(ControlledGetOutcome::Sandbox(Box::new(live))); + let runtime = test_runtime(driver).await; + runtime.store.put_message(&sandbox).await.unwrap(); + register_test_supervisor_session(&runtime, sandbox.object_id()); + runtime + .supervisor_session_connected(sandbox.object_id(), "replacement") + .await + .unwrap(); + + runtime.apply_sandbox_update(exited).await.unwrap(); + + let stored = runtime + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!( + stored.phase(), + if live_exited { + SandboxPhase::Error + } else { + SandboxPhase::Ready + } as i32, + "only a live {reason} may fail the replacement runtime" + ); + let status = stored.status.unwrap(); + assert_eq!(status.main_process_instance_id, "replacement"); + assert_eq!(status.restart_count, 1); + assert_eq!(status.exit_code, None); + } + } + #[tokio::test] async fn stale_ready_snapshot_queued_before_start_is_revalidated() { let driver = ControlledDriver::new(); @@ -13375,7 +13439,8 @@ mod tests { #[tokio::test] async fn unexpected_term_runtime_restart_transitions_to_error() { - let runtime = test_runtime(Arc::new(TestDriver::default())).await; + let driver = ControlledDriver::new(); + let runtime = test_runtime(driver.clone()).await; let sandbox = sandbox_record("sb-term-exit", "sandbox-term-exit", SandboxPhase::Ready); runtime.store.put_message(&sandbox).await.unwrap(); let mut runtime_restart = ready_driver_sandbox(sandbox.object_id(), sandbox.object_name()); @@ -13383,6 +13448,9 @@ mod tests { "ContainerRuntimeRestart", "container exited with status 143", ))); + driver.set_get_outcome(ControlledGetOutcome::Sandbox(Box::new( + runtime_restart.clone(), + ))); runtime.apply_sandbox_update(runtime_restart).await.unwrap(); diff --git a/deploy/docker/Dockerfile.gateway b/deploy/docker/Dockerfile.gateway index 0576c2e465..413605617a 100644 --- a/deploy/docker/Dockerfile.gateway +++ b/deploy/docker/Dockerfile.gateway @@ -8,7 +8,7 @@ # # Distroless Debian provides the glibc runtime required by the binary. -ARG GATEWAY_BASE_IMAGE=gcr.io/distroless/cc-debian13:nonroot@sha256:54df941ed0d06a1bd95ef5e0ce391fd8d9f94b64782dc9a60062727849ee3f97 +ARG GATEWAY_BASE_IMAGE=gcr.io/distroless/cc-debian13:nonroot@sha256:e792ab3d241a468a4fd7519ddbbebe66b49b5f365771716ea688ad40b6c6f1c2 FROM ${GATEWAY_BASE_IMAGE} AS gateway ARG TARGETARCH diff --git a/deploy/docker/Dockerfile.konflux.openclaw b/deploy/docker/Dockerfile.konflux.openclaw index 591a60adfb..ae0c8b574e 100644 --- a/deploy/docker/Dockerfile.konflux.openclaw +++ b/deploy/docker/Dockerfile.konflux.openclaw @@ -88,14 +88,14 @@ ENV HOME=/sandbox \ OPENCLAW_DISABLE_BONJOUR=1 \ OPENCLAW_OFFLINE=1 -LABEL com.redhat.component="odh-openshell-openclaw-container" \ - name="opendatahub/odh-openshell-openclaw" \ +LABEL com.redhat.component="odh-openshell-sandbox-openclaw-container" \ + name="opendatahub/odh-openshell-sandbox-openclaw" \ version="${OPENCLAW_VERSION}" \ summary="OpenClaw agent harness reference image for OpenShell sandboxes" \ description="Unsupported reference composition that runs the OpenClaw agent harness inside an OpenShell sandbox; not RHOAI product content" \ maintainer="['managed-open-data-hub@redhat.com']" \ - io.k8s.display-name="odh-openshell-openclaw" \ - io.k8s.description="odh-openshell-openclaw" \ + io.k8s.display-name="odh-openshell-sandbox-openclaw" \ + io.k8s.description="odh-openshell-sandbox-openclaw" \ io.openshift.expose-services="" \ io.openshift.tags="openshell,sandbox,openclaw,agent" \ help="See the README for usage: https://github.com/opendatahub-io/openshell/blob/main/deploy/konflux/openclaw/README.md" \ diff --git a/deploy/docker/Dockerfile.supervisor b/deploy/docker/Dockerfile.supervisor index 26fa44a765..e0483d5094 100644 --- a/deploy/docker/Dockerfile.supervisor +++ b/deploy/docker/Dockerfile.supervisor @@ -8,7 +8,8 @@ # Keep the GNU runtime and CA roots without a shell or unused native TLS libraries. # The default variant preserves UID 0 and /; compute drivers set the runtime UID. -FROM gcr.io/distroless/base-nossl-debian13@sha256:af5cb8dd589b8520b8c06bebb9efb73d7e16406cab58e85c51761fff49d370a0 AS supervisor +ARG SUPERVISOR_BASE_IMAGE=gcr.io/distroless/base-nossl-debian13@sha256:af5cb8dd589b8520b8c06bebb9efb73d7e16406cab58e85c51761fff49d370a0 +FROM ${SUPERVISOR_BASE_IMAGE} AS supervisor ARG TARGETARCH diff --git a/deploy/konflux/openclaw/README.md b/deploy/konflux/openclaw/README.md index cf98278166..d21f1a81bd 100644 --- a/deploy/konflux/openclaw/README.md +++ b/deploy/konflux/openclaw/README.md @@ -1,6 +1,6 @@ # OpenClaw reference harness image -`odh-openshell-openclaw` runs the [OpenClaw](https://github.com/openclaw/openclaw) agent harness inside an OpenShell sandbox. It is built from `deploy/docker/Dockerfile.konflux.openclaw` and the files in this directory. +`odh-openshell-sandbox-openclaw` runs the [OpenClaw](https://github.com/openclaw/openclaw) agent harness inside an OpenShell sandbox. It is built from `deploy/docker/Dockerfile.konflux.openclaw` and the files in this directory. ## Status @@ -42,7 +42,7 @@ Create an interactive sandbox. On first run, `openclaw-start` onboards OpenClaw ```shell openshell sandbox create --name openclaw \ - --from quay.io/opendatahub/odh-openshell-openclaw@sha256: \ + --from quay.io/opendatahub/odh-openshell-sandbox-openclaw@sha256: \ --provider model \ --env MODEL_BASE_URL=http://..svc.cluster.local:8000/v1 \ --env MODEL_ID= \ @@ -53,7 +53,7 @@ For headless use, onboard in a detached sandbox and run one-shot turns with `age ```shell openshell sandbox create --name openclaw \ - --from quay.io/opendatahub/odh-openshell-openclaw@sha256: \ + --from quay.io/opendatahub/odh-openshell-sandbox-openclaw@sha256: \ --provider model \ --env MODEL_BASE_URL=http://..svc.cluster.local:8000/v1 \ --env MODEL_ID= \ @@ -118,7 +118,7 @@ kind: ImageSetConfiguration apiVersion: mirror.openshift.io/v2alpha1 mirror: additionalImages: - - name: quay.io/opendatahub/odh-openshell-openclaw@sha256: + - name: quay.io/opendatahub/odh-openshell-sandbox-openclaw@sha256: ``` ## Build your own on UBI @@ -153,7 +153,7 @@ The npm inputs come from AIPCC agentic commit dc756bc3 (OpenClaw 2026.9.5). AIPC ## Konflux -The Tekton pipelines follow once the component is registered in odh-konflux-central. The build is hermetic for linux/x86_64 and linux/arm64 with this prefetch input: +odh-konflux-central owns the Tekton pipelines. Its onboarder sync opens a PR here that copies `pipelineruns/openshell/odh-openshell-sandbox-openclaw-*.yaml` over `.tekton/`, so change their params there and run the sync. Merges to `main` publish `odh-stable`, and each `v*-rhaiv.*` tag rebuilds the same pinned OpenClaw release under that tag; only the commit named in [Status](#status) is verified. The build is hermetic for linux/x86_64 and linux/arm64 with this prefetch input: ```json [{"type": "npm", "path": "deploy/konflux/openclaw"}, {"type": "rpm", "path": "deploy/konflux/openclaw"}] diff --git a/deploy/konflux/openclaw/model-provider-profile.yaml b/deploy/konflux/openclaw/model-provider-profile.yaml index c3efc81e3c..8c7cc3e4c3 100644 --- a/deploy/konflux/openclaw/model-provider-profile.yaml +++ b/deploy/konflux/openclaw/model-provider-profile.yaml @@ -2,8 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 # Example provider profile for the OpenClaw reference image -# (odh-openshell-openclaw). OpenShell does not load it; copy it, edit it and -# import it explicitly: +# (odh-openshell-sandbox-openclaw). OpenShell does not load it; copy it, edit +# it and import it explicitly: # openshell profile lint -f model-provider-profile.yaml # openshell profile import -f model-provider-profile.yaml --global # openshell provider create --name model --type openclaw-model \ @@ -15,7 +15,7 @@ # get the credential. # # Client binaries: node (OpenClaw runs on the image's Node.js). -# Reference layout: /usr/bin/node in odh-openshell-openclaw. +# Reference layout: /usr/bin/node in odh-openshell-sandbox-openclaw. # Credential scope: CUSTOM_API_KEY, sent as a bearer authorization header to # the host:port below and nowhere else. openclaw-start # stores it in the OpenClaw config as an env reference, so diff --git a/deploy/konflux/openclaw/smoke-test.sh b/deploy/konflux/openclaw/smoke-test.sh index b788a06094..45a0bf1412 100755 --- a/deploy/konflux/openclaw/smoke-test.sh +++ b/deploy/konflux/openclaw/smoke-test.sh @@ -5,8 +5,8 @@ set -euo pipefail -# Contract smoke tests for a built odh-openshell-openclaw image. Called by -# build-local.sh after a build; also runnable by hand. +# Contract smoke tests for a built odh-openshell-sandbox-openclaw image. +# Called by build-local.sh after a build; also runnable by hand. # # Usage: smoke-test.sh # @@ -102,8 +102,8 @@ got_harness_version=$(label io.openshell.harness.version) got_s2i1=$(label io.openshift.s2i.scripts-url) got_s2i2=$(label io.s2i.scripts-url) got_license=$(label com.redhat.license_terms) -if [[ "${got_name}" == "opendatahub/odh-openshell-openclaw" \ - && "${got_component}" == "odh-openshell-openclaw-container" \ +if [[ "${got_name}" == "opendatahub/odh-openshell-sandbox-openclaw" \ + && "${got_component}" == "odh-openshell-sandbox-openclaw-container" \ && "${got_harness}" == "openclaw" \ && -n "${got_harness_version}" \ && -z "${got_s2i1}" \ diff --git a/docs/how-it-works/sandboxes/runtimes.mdx b/docs/how-it-works/sandboxes/runtimes.mdx index a8aec4fb4f..b3b9fa0511 100644 --- a/docs/how-it-works/sandboxes/runtimes.mdx +++ b/docs/how-it-works/sandboxes/runtimes.mdx @@ -44,6 +44,11 @@ driver TOML overrides the compiled release defaults. Helm renders Kubernetes image fields into TOML, so they apply when these environment variables are unset. Sandbox requests cannot override either trusted image. +When building your own gateway or supervisor image, the `GATEWAY_BASE_IMAGE` +and `SUPERVISOR_BASE_IMAGE` build arguments override their pinned bases. See +[Container Image Base Overrides](https://github.com/NVIDIA/OpenShell/blob/main/CONTRIBUTING.md#container-image-base-overrides) +for build commands and base compatibility requirements. + For TLS-enabled Docker, Podman, and MicroVM gateways, set `guest_tls_ca` in `[openshell.gateway]` or use the package-managed local CA. Remove the retired `guest_tls_cert` and `guest_tls_key` fields. Supervisors receive only the diff --git a/e2e/rust/tests/sandbox_lifecycle.rs b/e2e/rust/tests/sandbox_lifecycle.rs index 5e708e097c..b299a5d1d7 100644 --- a/e2e/rust/tests/sandbox_lifecycle.rs +++ b/e2e/rust/tests/sandbox_lifecycle.rs @@ -9,9 +9,11 @@ use std::process::Stdio; use std::time::Duration; use openshell_e2e::harness::binary::{openshell_cmd, openshell_tty_cmd}; -use openshell_e2e::harness::cli::{run_cli, wait_for_sandbox_phase}; +use openshell_e2e::harness::cli::{ + run_cli, wait_for_sandbox_exec_contains, wait_for_sandbox_phase, +}; use openshell_e2e::harness::output::{extract_field, strip_ansi}; -use openshell_e2e::harness::sandbox::{SandboxGuard, unique_sandbox_name}; +use openshell_e2e::harness::sandbox::{E2E_WORKLOAD_IMAGE, SandboxGuard, unique_sandbox_name}; use serial_test::serial; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::time::{Instant, sleep}; @@ -1300,45 +1302,81 @@ async fn canonical_main_exit_255_is_not_retried_as_transport_failure() { #[tokio::test] #[serial(sandbox_lifecycle)] async fn on_failure_policy_replaces_runtime_and_preserves_workspace() { - const FIRST_MARKER: &str = "initial-main-ready"; const SCRIPT: &str = r#" marker=/sandbox/.openshell-restart-e2e if [ -e "$marker" ]; then printf 'replacement-%s\n' "$(cat /proc/sys/kernel/random/uuid)" > /sandbox/replacement-run - printf 'replacement-main-ready\n' sleep 300 else touch "$marker" printf 'initial-%s\n' "$(cat /proc/sys/kernel/random/uuid)" > /sandbox/initial-run - printf 'initial-main-ready\n' - sleep 2 + while [ ! -e /sandbox/.openshell-restart-release ]; do sleep 0.05; done exit 17 fi "#; - let mut sandbox = SandboxGuard::create_keep_with_args( - &["--restart-policy", "on-failure"], - &["sh", "-lc", SCRIPT], - FIRST_MARKER, + let mut sandbox = SandboxGuard::manage_existing(unique_sandbox_name()); + let (output, exit_code) = run_cli(&[ + "sandbox", + "create", + "--name", + &sandbox.name, + "--from", + E2E_WORKLOAD_IMAGE, + "--detach", + "--restart-policy", + "on-failure", + "--", + "sh", + "-lc", + SCRIPT, + ]) + .await; + assert_eq!( + exit_code, 0, + "create sandbox with OnFailure policy: {output}" + ); + wait_for_sandbox_exec_contains( + &sandbox.name, + &["cat", "/sandbox/initial-run"], + "initial-", + SANDBOX_RESTART_TIMEOUT, ) .await - .expect("create sandbox with OnFailure restart policy"); + .expect("initial main process should write its workspace marker"); + + let initial_details = sandbox_details(&sandbox.name).await; + let initial_instance = extract_field(&initial_details, "Main process instance") + .expect("initial main process instance"); + sandbox + .exec(&["touch", "/sandbox/.openshell-restart-release"]) + .await + .expect("release initial main process"); let deadline = Instant::now() + SANDBOX_RESTART_TIMEOUT; - let mut observed_replacement_starting = false; + let mut last_runs = Err("replacement is not ready".to_string()); let final_details = loop { let details = sandbox_details(&sandbox.name).await; - observed_replacement_starting |= details.contains("Phase: Starting"); - if observed_replacement_starting - && details.contains("Phase: Ready") - && details.contains("Restart count: 1") + let replacement_instance = extract_field(&details, "Main process instance") + .is_some_and(|instance| instance != "-" && instance != initial_instance); + if details.contains("Phase: Ready") + && extract_field(&details, "Restart count").as_deref() == Some("1") + && replacement_instance { - break details; + last_runs = sandbox + .exec(&["cat", "/sandbox/initial-run", "/sandbox/replacement-run"]) + .await; + if let Ok(runs) = &last_runs + && runs.contains("initial-") + && runs.contains("replacement-") + { + break details; + } } assert!( Instant::now() < deadline, "sandbox did not complete its policy-driven restart within \ - {SANDBOX_RESTART_TIMEOUT:?}; last details:\n{details}" + {SANDBOX_RESTART_TIMEOUT:?}; last details:\n{details}\nworkspace: {last_runs:?}" ); sleep(Duration::from_millis(250)).await; }; @@ -1347,19 +1385,6 @@ fi final_details.contains("Restart policy: on-failure"), "restart policy should remain visible after replacement:\n{final_details}" ); - let runs = sandbox - .exec(&["cat", "/sandbox/initial-run", "/sandbox/replacement-run"]) - .await - .expect("replacement sandbox should retain the first run's workspace"); - assert!( - runs.contains("initial-"), - "missing initial run marker:\n{runs}" - ); - assert!( - runs.contains("replacement-"), - "missing replacement run marker:\n{runs}" - ); - sandbox.cleanup().await; } diff --git a/scripts/agents/gator/README.md b/scripts/agents/gator/README.md index a683a2030b..243e41f124 100644 --- a/scripts/agents/gator/README.md +++ b/scripts/agents/gator/README.md @@ -37,8 +37,10 @@ The launcher: - Scans `profile_paths` in manifest order and imports or updates `providers/github-gator.yaml`. - Creates or updates the `github-gator` provider from `gh auth token`. - Selects the requested harness and bakes the common runtime into the immutable sandbox payload. +- Builds with the latest Codex CLI by default. Docker can reuse the cached install layer; rebuild with that layer's cache disabled to fetch a newer release. - For `--harness codex`, imports `providers/codex-gator.yaml`, creates or updates the `codex-gator` provider from `$HOME/.codex/auth.json`, and stores the refresh token as gateway-only refresh material. - For `--harness codex`, configures gateway-managed refresh for `CODEX_AUTH_ACCESS_TOKEN` and rotates it before launching the sandbox. +- Passes the non-secret Codex account ID from host auth as `CODEX_ACCOUNT_ID`. The harness writes it literally into `auth.json` for local workspace discovery; access tokens remain opaque provider placeholders and refresh tokens remain gateway-only. - Enables `agent_policy_proposals_enabled` and `proposal_approval_mode=auto` at gateway scope. - Uses the gator image policy copied to `/etc/openshell/policy.yaml`. - Installs the gator-specific `gh` wrapper from `gator/bin/gh` as `/usr/local/bin/gh` to fail closed when same-head-SHA history cannot be checked, prevent duplicate dispositions, and require versioned review payloads. diff --git a/scripts/agents/gator/agent.yaml b/scripts/agents/gator/agent.yaml index c375c9bddc..18f555ff15 100644 --- a/scripts/agents/gator/agent.yaml +++ b/scripts/agents/gator/agent.yaml @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 id: gator -payload_version: 9 +payload_version: 10 display_name: Gator Gate Agent description: Validate and monitor OpenShell GitHub issues and pull requests through the gator state machine. diff --git a/scripts/agents/run.sh b/scripts/agents/run.sh index 75f96565b5..d778d33a72 100755 --- a/scripts/agents/run.sh +++ b/scripts/agents/run.sh @@ -801,7 +801,12 @@ HARNESS_ENV_ARGS=( case "$HARNESS" in codex) + # Codex compares account IDs locally during workspace discovery. Unlike + # bearer tokens, this non-secret identifier must not be a placeholder. + [[ -n "${CODEX_AUTH_ACCOUNT_ID:-}" ]] || fail "missing Codex account ID" + [[ "$CODEX_AUTH_ACCOUNT_ID" != openshell:resolve:* ]] || fail "Codex account ID must be a literal identifier" HARNESS_ENV_ARGS+=( + "CODEX_ACCOUNT_ID=$CODEX_AUTH_ACCOUNT_ID" "CODEX_MODEL=${CODEX_MODEL:-$HARNESS_MODEL}" "CODEX_REASONING=${CODEX_REASONING:-$HARNESS_REASONING}" ) diff --git a/scripts/agents/runtime/harnesses/codex/exec.sh b/scripts/agents/runtime/harnesses/codex/exec.sh index ef92258795..8726714d68 100755 --- a/scripts/agents/runtime/harnesses/codex/exec.sh +++ b/scripts/agents/runtime/harnesses/codex/exec.sh @@ -16,7 +16,8 @@ require_env() { } require_env CODEX_AUTH_ACCESS_TOKEN -require_env CODEX_AUTH_ACCOUNT_ID +require_env CODEX_ACCOUNT_ID +[[ "$CODEX_ACCOUNT_ID" != openshell:resolve:* ]] || { echo "Codex account ID must be a literal identifier" >&2; exit 1; } require_env GITHUB_TOKEN PROMPT_FILE="$1" @@ -58,7 +59,8 @@ fs.writeFileSync(path, JSON.stringify({ id_token: providerValue("CODEX_AUTH_ID_TOKEN") || fallbackIdToken, access_token: providerValue("CODEX_AUTH_ACCESS_TOKEN"), refresh_token: providerValue("CODEX_AUTH_REFRESH_TOKEN") || "gateway-managed-refresh-token", - account_id: providerValue("CODEX_AUTH_ACCOUNT_ID"), + // Workspace discovery compares this non-secret identifier locally. + account_id: process.env.CODEX_ACCOUNT_ID, }, last_refresh: new Date().toISOString(), }, null, 2)); diff --git a/scripts/agents/runtime/harnesses/codex/exec_test.sh b/scripts/agents/runtime/harnesses/codex/exec_test.sh index 6f1b998217..8c2fc5cdef 100755 --- a/scripts/agents/runtime/harnesses/codex/exec_test.sh +++ b/scripts/agents/runtime/harnesses/codex/exec_test.sh @@ -11,6 +11,7 @@ trap 'rm -rf "$TMP_DIR"' EXIT ACCESS_PLACEHOLDER="openshell:resolve:env:s$(printf 'a%.0s' {1..64})_CODEX_AUTH_ACCESS_TOKEN" ACCOUNT_PLACEHOLDER="openshell:resolve:env:v42_CODEX_AUTH_ACCOUNT_ID" +ACCOUNT_ID="test-codex-account" cat > "$TMP_DIR/codex" <<'MOCK' #!/usr/bin/env bash @@ -23,8 +24,11 @@ fi access="$(jq -r '.tokens.access_token' "$HOME/.codex/auth.json")" account="$(jq -r '.tokens.account_id' "$HOME/.codex/auth.json")" [[ "$access" == "$EXPECTED_ACCESS_PLACEHOLDER" ]] -[[ "$account" == "$EXPECTED_ACCOUNT_PLACEHOLDER" ]] -printf '%s\n' 'ok - Codex auth preserves opaque provider placeholders' +[[ "$account" == "$EXPECTED_ACCOUNT_ID" ]] +[[ "$account" != openshell:resolve:* ]] +[[ "$(jq -r '.tokens.refresh_token' "$HOME/.codex/auth.json")" == "gateway-managed-refresh-token" ]] +[[ "$(node -e 'console.log((require("fs").statSync(process.env.HOME + "/.codex/auth.json").mode & 0o777).toString(8))')" == 600 ]] +printf '%s\n' 'ok - Codex auth preserves token placeholders and uses a literal account ID' MOCK chmod +x "$TMP_DIR/codex" printf '%s\n' 'test prompt' > "$TMP_DIR/prompt.md" @@ -33,7 +37,23 @@ OPENSHELL_AGENT_HOME="$TMP_DIR/home" \ CODEX_BIN="$TMP_DIR/codex" \ CODEX_AUTH_ACCESS_TOKEN="$ACCESS_PLACEHOLDER" \ CODEX_AUTH_ACCOUNT_ID="$ACCOUNT_PLACEHOLDER" \ +CODEX_ACCOUNT_ID="$ACCOUNT_ID" \ GITHUB_TOKEN="openshell:resolve:env:v42_GITHUB_TOKEN" \ EXPECTED_ACCESS_PLACEHOLDER="$ACCESS_PLACEHOLDER" \ -EXPECTED_ACCOUNT_PLACEHOLDER="$ACCOUNT_PLACEHOLDER" \ +EXPECTED_ACCOUNT_ID="$ACCOUNT_ID" \ bash "$SCRIPT_DIR/exec.sh" "$TMP_DIR/prompt.md" + +# Fail before launching Codex when the routing identity is absent or opaque. +for invalid_account_id in "" "$ACCOUNT_PLACEHOLDER"; do + if OPENSHELL_AGENT_HOME="$TMP_DIR/invalid-home" \ + CODEX_BIN="$TMP_DIR/codex" \ + CODEX_AUTH_ACCESS_TOKEN="$ACCESS_PLACEHOLDER" \ + CODEX_ACCOUNT_ID="$invalid_account_id" \ + GITHUB_TOKEN="placeholder" \ + bash "$SCRIPT_DIR/exec.sh" "$TMP_DIR/prompt.md" >"$TMP_DIR/error" 2>&1; then + echo "unexpected success with invalid Codex account ID" >&2 + exit 1 + fi + [[ ! -e "$TMP_DIR/invalid-home/.codex/auth.json" ]] +done +printf '%s\n' 'ok - missing and placeholder account IDs are rejected' diff --git a/sdk/typescript/package-lock.json b/sdk/typescript/package-lock.json index 6c3664cedd..52bf056287 100644 --- a/sdk/typescript/package-lock.json +++ b/sdk/typescript/package-lock.json @@ -1659,9 +1659,9 @@ "license": "ISC" }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { diff --git a/skills/debug-openshell-cluster/SKILL.md b/skills/debug-openshell-cluster/SKILL.md index 6e301ce6d9..ff5c9fcdae 100644 --- a/skills/debug-openshell-cluster/SKILL.md +++ b/skills/debug-openshell-cluster/SKILL.md @@ -93,6 +93,11 @@ Use gateway metadata, deployment values, or the user's setup notes to identify t Before debugging the compute platform, inspect gateway logs for failures in dependencies initialized before the listener becomes ready. +The gateway container uses a Distroless Debian runtime. For OS-library +vulnerability findings, check the deployed image digest and package version; +deploy a rebuilt gateway image with the patched base. Updating the gateway +binary alone does not update the libraries supplied by its container image. + For resource-admission failures, distinguish disabled caller driver config from missing resource approval. Helm defaults `server.drivers.kubernetes.allowDriverConfig` to false and `resourceAdmission.enabled` to true. Existing PVCs, RuntimeClasses, @@ -213,6 +218,8 @@ rationale, configured and effective modes, active generation, and the explicit `previous_policy_active` state. The published supervisor image uses a shell-free distroless Debian 13 base. +For custom builds using `SUPERVISOR_BASE_IMAGE`, check the selected base's GNU +runtime libraries, CA certificates, and inherited user and working directory. Use container logs, engine inspection and the configured exec health probe for diagnostics; `exec ... sh`, package installation and in-container shell scripts are unavailable. Workload shells belong to the separate sandbox image. Preserve