Skip to content

fix(deployments): deliver config_files on the openshell backend (AIRCORE-999) - #1145

Merged
maxdubrinsky merged 2 commits into
mainfrom
mdubrinsky/aircore-999-config_files-is-accepted-and-validated-then-silently
Aug 7, 2026
Merged

fix(deployments): deliver config_files on the openshell backend (AIRCORE-999)#1145
maxdubrinsky merged 2 commits into
mainfrom
mdubrinsky/aircore-999-config_files-is-accepted-and-validated-then-silently

Conversation

@maxdubrinsky

@maxdubrinsky maxdubrinsky commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

DeploymentConfig.config_files is a first-class API field (accepted, validated, given a default mode, echoed back), but only the k8s backend implemented it. The openshell backend accepted the field, reached READY, and never wrote the files: a silent drop, so a caller had every reason to believe its config was delivered. This makes openshell deliver the files, or fail loudly when it cannot.

Related Issue

AIRCORE-999.

Changes

  • Deliver each config_files entry into the sandbox before serve launch via ExecSandbox, with the content on the RPC's first-class stdin bytes field (sh -c 'set -e; mkdir -p <dir>; cat > <path>; chmod <mode> <path>'). The content is streamed, so it never touches the argv nor the single-line, size-capped sandbox environment. _exec_detached gained a stdin kwarg.
  • Delivery runs inside the once-only launch guard in _advance_provisioning, immediately before launch, so files are written exactly once per deployment. Delivery runs as the non-root sandbox user (ExecSandbox has no user field; the policy pins run_as_user), so a target that user cannot write (e.g. /workspace, which the packaged agent image chowns to agent even though the sandbox policy lists it read-write) fails with a terminal FAILED naming the path and the shell's error, and the sandbox is torn down, rather than reaching READY file-less.
  • Delivery is self-attesting: the script prints a completion marker only after set -e clears mkdir/cat/chmod, and a write is accepted only when that marker is present in the output. This closes the residual silent-drop path where an ExecSandbox stream ends without an exit event, without changing the shared exec probes' "only positive evidence flaps a deployment" semantics.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with documentation updates
  • Documentation only
  • Contributor tooling or automation
  • CI, build, or test infrastructure

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Documentation updated for user-visible behavior
  • Documentation not applicable — justification: no user-visible API or CLI surface change. A doc note on the openshell writable-path constraint can ride the docs work already tracked under AIRCORE-981.

Verification

  • Pull request title follows the repository's Conventional Commit format
  • Every commit includes an appropriate Signed-off-by: trailer
  • uv run pre-commit run -a passes, or any blocked checks are identified below
  • Targeted tests pass, or tests are marked not applicable above
  • No secrets, API keys, or credentials are included

Targeted validation:

  • pre-commit run --files <changed files>: ruff, ruff format, ty, copyright headers, plugin-import guard, and merge-conflict hooks all pass; the remaining hooks skip as not-applicable. Run scoped to the changed files rather than -a because a full workspace sync could not be built in this worktree (a transitive dependency needs Python.h); the applicable hooks are the ones exercised above.
  • pytest plugins/nemo-deployments/tests/unit/backends/openshell: 93 passed. Adversarial cases include stdin-streaming with delivery-before-launch, multi-file, octal mode, shell-metacharacter content that must not be interpreted, spaced-path quoting, unwritable /workspace failing loudly and deleting the sandbox, rpc-error failing loudly, unconfirmed-delivery (no completion marker) failing loudly, multi-file partial failure, empty-list no-op, and write-exactly-once.
  • Full plugins/nemo-deployments/tests/unit: passes except test_ports.py::test_find_available_port_excludes_pending_assignments, a pre-existing real-socket flake that reproduces identically on unmodified main (host port 9000 is occupied on the dev box) and is unrelated to this change, which touches only the openshell backend.

Notes

Docker delivery and deleting the nemo-agents NAT_CONFIG_YAML env-var workaround are deferred to a follow-up ticket, along with a possible fileset-id delivery path for large artifacts. This PR scopes to the openshell backend, whose delivery mechanism is proven.

Summary by CodeRabbit

  • New Features

    • Deployment configuration files are now delivered to the sandbox before the service starts.
    • Files are written to their configured paths with the correct permissions.
    • Multiple configuration files are supported and delivered in the correct order.
  • Bug Fixes

    • Deployment now stops safely when configuration delivery fails, is incomplete, or cannot be verified.
    • Failed deployments clean up the sandbox and do not launch the service.

…ORE-999)

The openshell backend accepted DeploymentConfig.config_files, reached READY,
and never wrote the files -- a silent drop, since only the k8s backend
implemented the field.

Deliver each config file into the sandbox before serve launch via ExecSandbox
with the content on the RPC's stdin field (cat > path; chmod), so the bytes are
opaque to the shell: no argv escaping, no single-line/size-capped env. Delivery
runs as the non-root sandbox user, so a target it cannot write (e.g. /workspace,
which the packaged image chowns to 'agent') fails loudly with the path and the
shell's error and tears the sandbox down, rather than reaching READY with the
file absent.

Docker delivery and deleting the nemo-agents NAT_CONFIG_YAML workaround are
deferred to a follow-up.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
@maxdubrinsky
maxdubrinsky marked this pull request as ready for review August 6, 2026 21:47
@maxdubrinsky
maxdubrinsky requested review from a team as code owners August 6, 2026 21:47
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a7090d69-f093-4ed5-9a1b-d6e2baba4b4c

📥 Commits

Reviewing files that changed from the base of the PR and between 467f71b and 9db26a3.

📒 Files selected for processing (2)
  • plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/backend.py
  • plugins/nemo-deployments/tests/unit/backends/openshell/test_openshell_backend_mocked.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/backend.py

📝 Walkthrough

Walkthrough

The OpenShell backend streams declared ConfigFile contents through ExecSandbox before launching the serve process. It validates completion markers and exit results, reports delivery failures, deletes failed sandboxes, and adds mocked coverage for success and failure cases.

Changes

OpenShell config delivery

Layer / File(s) Summary
Config delivery execution
plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/backend.py, plugins/nemo-deployments/tests/unit/backends/openshell/test_openshell_backend_mocked.py
The backend streams UTF-8 file content through stdin. The delivery script creates directories, writes content, applies octal modes, quotes paths, and emits a completion marker after success.
Launch-path integration
plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/backend.py, plugins/nemo-deployments/tests/unit/backends/openshell/test_openshell_backend_mocked.py
Declared files are delivered once before launch. Deployments without files retain the existing flow. Delivery failures report the affected path, delete the sandbox, and skip launch.
Delivery failure validation
plugins/nemo-deployments/tests/unit/backends/openshell/test_openshell_backend_mocked.py
Tests cover incomplete streams, missing markers, RPC errors, and partial multi-file failures. Tests also cover success when the completion marker is present without an exit event.

Sequence Diagram(s)

sequenceDiagram
  participant OpenShellBackend
  participant ExecSandbox
  participant DeliveryScript
  participant ServeProcess
  OpenShellBackend->>ExecSandbox: stream config content and delivery command
  ExecSandbox->>DeliveryScript: create directories and write config file
  DeliveryScript-->>ExecSandbox: return completion marker and exit status
  ExecSandbox-->>OpenShellBackend: return delivery result
  OpenShellBackend->>ServeProcess: launch after successful delivery
  OpenShellBackend->>ExecSandbox: delete sandbox after delivery failure
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: delivering config files on the OpenShell backend.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mdubrinsky/aircore-999-config_files-is-accepted-and-validated-then-silently

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/backend.py`:
- Around line 507-514: Make the serve startup sequence in read_status atomic
across concurrent calls by protecting _serve_launched, _deliver_config_files,
and the subsequent serve launch with a sandbox-wide lock or atomic launch
marker. Ensure only one caller can probe, deliver configuration files, and
launch the serve process, while concurrent callers observe the established
launch state and skip relaunching.
- Around line 712-724: Update the config delivery validation around
_CONFIG_DELIVERED_MARKER to require exit_code == 0, rejecting exit_code is None
even when the completion marker appears in output. Add a test covering
_stream_without_exit(stdout=_CONFIG_DELIVERED_MARKER) and assert the delivery
fails without launching the serve command.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c4f073fb-7fe7-4d90-86ed-eae04c8c17f8

📥 Commits

Reviewing files that changed from the base of the PR and between c71ca67 and 467f71b.

📒 Files selected for processing (2)
  • plugins/nemo-deployments/src/nemo_deployments_plugin/backends/openshell/backend.py
  • plugins/nemo-deployments/tests/unit/backends/openshell/test_openshell_backend_mocked.py

@mckornfield mckornfield left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wonder if tests could be DRY a bit but 🤷 maybe it will look worse

@ironcommit ironcommit left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Extract the inline delivery one-liner into a pure _delivery_script(path,
mode) helper and add two behavioral tests that run the exact script the
backend generates through a real /bin/sh:

- adversarial content on stdin lands in the file byte-for-byte while the
  shell-injection payloads never execute (a seeded canary is untouched),
  proving the stdin path keeps untrusted content inert
- a read-only target makes the shell exit non-zero without the completion
  marker, exercising the real FAILED branch instead of a mocked stream

Tighten the delivery docstrings and comments per review: drop change-history
narration and the cut-off /workspace phrasing, state behavior directly.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
@github-actions github-actions Bot added the fix label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 31464/40086 78.5% 63.1%
Integration Tests 18326/38038 48.2% 20.8%

@maxdubrinsky
maxdubrinsky added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit 6ff9d94 Aug 7, 2026
54 checks passed
@maxdubrinsky
maxdubrinsky deleted the mdubrinsky/aircore-999-config_files-is-accepted-and-validated-then-silently branch August 7, 2026 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants