Commit Graph
269 Commits
Author SHA1 Message Date
John T. MyersandJohn Myers 3d0c4d17e1 fix(security): add SSH session token expiry, connection limits, and lifecycle cleanup (#182)
* fix(security): add SSH session token expiry, connection limits, and lifecycle cleanup

Closes #22

SSH session tokens previously had no TTL and remained valid indefinitely.
This adds configurable token expiry (default 24h), per-token (10) and
per-sandbox (20) concurrent connection limits, session cleanup on sandbox
deletion, and a background reaper for expired/revoked sessions.

* fix(security): lower per-token concurrent connection limit from 10 to 3

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-09 11:39:23 -07:00
Miyoung Choi 574ef18dfc docs: consolidate information architecture and author content (#124)
* initial doc filling

* improvements

* stage provided get started, and add clean tutorials

* pull in Kirit's content and polish

* improve observability

* moving pieces

* move TOC around

* drop support matrix from concepts

* fix links

* minor fixes and fix badges

* minor fixes

* incorporate missed content

* minor improvements

* clean up

* run dori style guide review

* clean up

* updates impacting docs

* incorporate feedback

* minor fix

* some edits

* enterprise structure

* update cards

* improve

* add some emojis

* improve landing page with animated getting started code

* fix the animated code

* small improvements

* refresh content based on PR 156 and 158

* README as the source of truth for quickstart

* update README

* run edits

* change to the new prod name, text only, code swipe later

* add Home

* incorporate dev feedback on README

* krit's edits

* edit and improve index pages

* fix build

* revert README

* revert quickstart to not pull from README
2026-03-09 11:33:56 -07:00
Drew Newberry ed53c35d2b feat(cli): improve sandbox provisioning status messages and UX (#175) 2026-03-08 16:36:50 -07:00
Drew Newberry ffeaf0dd59 fix(sandbox): fix create ordering race, dual-registry credentials, and policy identity clearing (#176) 2026-03-08 15:57:00 -07:00
Drew Newberry ba78e278a8 feat(cli): switch community sandbox registry to CloudFront CDN (#170) 2026-03-07 19:52:27 -08:00
Piotr Mlocek 5177acca36 fix(cli): scope git-aware sandbox uploads to requested path (#171) 2026-03-07 19:08:30 -08:00
Piotr Mlocek ec89fec74d fix(ci): use docker-safe publish image tags (#169) 2026-03-07 06:03:21 -08:00
John T. MyersandJohn Myers b8d873f68f feat(proxy): support plain HTTP forward proxy for private IP endpoints (#158)
* feat(proxy): support plain HTTP forward proxy for private IP endpoints

Add forward proxy mode to the sandbox proxy so that standard HTTP
libraries (httpx, requests, etc.) work with HTTP_PROXY for plain HTTP
calls to private IP endpoints. Previously, non-CONNECT methods were
unconditionally rejected with 403.

The forward proxy path requires all three conditions to be met:
- OPA policy explicitly allows the destination
- The matched endpoint has allowed_ips configured
- All resolved IPs are RFC 1918 private

This ensures plain HTTP never reaches the public internet while enabling
seamless access to internal services without custom CONNECT tunnel code.

Implementation:
- parse_proxy_uri(): parses absolute-form URIs into components
- rewrite_forward_request(): rewrites to origin-form, strips hop-by-hop
  headers, adds Via and Connection: close
- handle_forward_proxy(): full handler with OPA eval, SSRF checks,
  private-IP gate, upstream connect, and bidirectional relay
- Updated dispatch in handle_tcp_connection to route non-CONNECT methods

Includes 14 unit tests and 6 E2E tests (FWD-1 through FWD-6).
CONNECT path remains completely untouched.

Closes #155

* fix(proxy): remove InspectForInference match arm removed by #146

The inference routing simplification in #146 reduced NetworkAction to
Allow/Deny, removing InspectForInference. Drop the dead match arm from
handle_forward_proxy.

* fix(sandbox): restore BestEffort as default Landlock compatibility

The Landlock V2 upgrade in #151 changed the default from BestEffort to
HardRequirement. This causes all proxy-mode sandboxes to crash with
Permission denied when the policy omits the landlock field, because the
child process gets locked to only /etc/navigator-tls and /sandbox.

Restore BestEffort as the default so policies without an explicit
landlock field degrade gracefully.

Fixes #161

* fix(sandbox): inject baseline filesystem paths for proxy-mode sandboxes

Proxy-mode sandboxes need baseline filesystem paths (/usr, /lib, /etc,
/app, /var/log read-only; /sandbox, /tmp read-write) for the child
process to function under Landlock. Without these, the child can't exec
binaries, resolve DNS, or load shared libraries.

The supervisor now enriches the policy with these baseline paths at
startup, covering both standalone (file) and gateway (gRPC) modes. For
gateway mode, the enriched policy is synced back so users see the
effective policy via 'nemoclaw sandbox get'.

The gateway validation is relaxed to allow additive filesystem changes
(new paths can be added, existing paths cannot be removed) to support
the supervisor's enrichment sync-back.

Includes 2 E2E tests: BFS-1 (missing filesystem_policy) and BFS-2
(incomplete filesystem_policy).

Fixes #161

* fix(e2e): update assertion for relaxed filesystem validation message

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-06 19:31:46 -08:00
Piotr Mlocek 12035387f5 fix(ci): drop unnecessary pipefail in docker build workflow (#166)
The reusable Docker build workflow runs under sh by default, and the compute-version step only uses a single command substitution. Removing pipefail avoids the illegal option error without changing the explicit tag-fetch setup.
2026-03-06 19:17:53 -08:00
Piotr Mlocek dcc7a09f0e fix(ci): standardize safe tag fetches (#165) 2026-03-06 18:58:49 -08:00
Piotr Mlocek 68525bb81a fix(build): propagate packaged version through cluster artifacts (#164)
Pass the computed cargo version through Docker and cluster packaging paths so deployed clusters report the built artifact version while keeping latest and explicit version tags aligned.
2026-03-06 18:17:03 -08:00
Drew Newberry 07b9d5d00e feat(cli): restructure CLI commands for simpler UX (#156) 2026-03-06 16:26:39 -08:00
Piotr Mlocek 31d7ca53ff refactor(inference): simplify routing — introduce inference.local, remove implicit catch-all (#146)
Remove multi-route CRUD system and replace with single managed cluster
route (inference.local). Key changes:

- Remove inference route CRUD RPCs and CLI commands
- Remove InspectForInference OPA action; policy is binary allow/deny
- Introduce AuthHeader enum and InferenceProviderProfile in navigator-core
- Router is now provider-agnostic: auth style carried on ResolvedRoute
- Replace InferenceRouteSpec with ClusterInferenceConfig (2 fields vs 8)
- Rename proto: routing_hint->name, SandboxResolvedRoute->ResolvedRoute,
  GetSandboxInferenceBundle->GetInferenceBundle, drop sandbox_id param
- Rename RouteConfig.route -> RouteConfig.name; use inference.local
- Add 'nemoclaw cluster inference update' for partial config changes
- Delete stale navigator.inference.v1.rs checked-in proto file
- Update architecture docs, agent skills, and CLI reference

Closes #133
2026-03-06 13:54:23 -08:00
Drew Newberry 91c7f84ccf feat(sandbox): upgrade Landlock to ABI V2 and fix sandbox venv PATH (#151) 2026-03-06 11:55:57 -08:00
Drew Newberry a8e9b43dcc refactor(e2e): replace bash e2e tests with Rust integration tests (#150) 2026-03-06 09:01:49 -08:00
John T. MyersandJohn Myers 890dfcc90f fix(server): add field-level size limits to sandbox and provider creation (#145)
* fix(server): add field-level size limits to sandbox and provider creation

Closes #24

Add validate_sandbox_spec and provider field validation with named
constants. Configure explicit 1MB tonic max_decoding_message_size.
Inference routes excluded per #133 rearchitecture.

* chore: remove issue number references from code comments

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-06 08:08:05 -08:00
Drew Newberry 338fa121c9 chore(cluster): upgrade k3s to v1.35.2 and remove K3S_VERSION from mise.toml (#152) 2026-03-06 01:35:06 -08:00
Drew Newberry 8c63413809 fix(docker): remediate container scan vulnerabilities across CI, cluster, and sandbox images (#144) 2026-03-05 22:08:31 -08:00
Drew Newberry bf28831673 ci: rename GHCR image paths from nv-agent-env to nemoclaw (#126) 2026-03-05 17:01:56 -08:00
John T. MyersandJohn Myers 5dd823a778 fix(server): clamp list RPC page limit to prevent unbounded queries (#140)
Closes #26

All list RPCs (ListSandboxes, ListProviders, ListSandboxPolicies,
ListInferenceRoutes) passed the client-provided limit directly to SQL
queries with no upper bound. A client could send limit=u32::MAX and
cause the server to load all records into memory, risking OOM. This
introduces a MAX_PAGE_SIZE constant (1000) and a clamp_limit helper
that caps the limit in every list handler before it reaches the
persistence layer.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-05 16:30:59 -08:00
Drew Newberry fc22cfd2f7 fix(cluster): replace openssl with /dev/urandom in cluster image (#139) 2026-03-05 16:20:19 -08:00
John T. MyersandJohn Myers ae9e766567 fix(server): prevent unbounded bus entry growth for sandbox IDs (#138)
Closes #27

Add remove() methods to TracingLogBus, PlatformEventBus, and
SandboxWatchBus to clean up entries when sandboxes are deleted.
Wire cleanup into both handle_deleted (K8s reconciler) and
delete_sandbox (gRPC handler). Reorder watch_sandbox to validate
sandbox existence before subscribing to buses, preventing entries
for non-existent IDs. Add one-time sandbox validation at stream
open in push_sandbox_logs.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-05 15:58:01 -08:00
John T. MyersandJohn Myers 024150e5c6 feat(policy): add validation layer to reject unsafe sandbox policies (#135)
* feat(policy): add validation layer to reject unsafe sandbox policies

Add policy validation that checks for root process identity, path
traversal sequences, overly broad filesystem paths, and exceeding
filesystem rule limits. Validation runs at three entry points:
disk-loaded YAML policies (fallback to restrictive default on violation),
gRPC CreateSandbox, and gRPC UpdateSandboxPolicy (returns
INVALID_ARGUMENT). Filesystem paths are normalized before storage to
collapse traversal components.

Closes #33

* fix(e2e): correct policy update test to match immutable field behavior

The update policy test was asserting on validation errors for fields
(process, filesystem) that are immutable on live sandboxes. The server
rejects changes to these fields before validation runs. Updated the test
to verify the immutability guard instead.

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-05 15:54:55 -08:00
John T. MyersandJohn Myers ff99fec819 refactor(tui): rebrand Gator to Term/NemoClaw (#134)
Closes #130

Rename the TUI from "Gator" to use generic naming:
- CLI subcommand: `nemoclaw gator` → `nemoclaw term`
- TUI title bar: "Gator" → "NemoClaw"
- Docs/skills: "Gator" → "the TUI" or "NemoClaw TUI"
- File renames: gator.toml → term.toml, gator.md → tui.md
- Remove dead link to nonexistent plans/gator-tui.md

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-05 15:13:13 -08:00
Drew Newberry dfe7ba732e fix(cluster): add openssl package to cluster image (#137)
The entrypoint script uses 'openssl rand -hex 32' to generate the SSH
handshake secret (added in e9f1071), but the rancher/k3s base image
does not include openssl. This causes the container to exit with code
127 immediately on startup.

Fixes #136
2026-03-05 14:43:03 -08:00
John T. MyersandJohn Myers 5e7a2f2dd8 fix(sandbox): verify effective UID/GID after privilege drop (#132)
Closes #32

Add post-condition checks in drop_privileges() to verify that setgid()
and setuid() actually changed the effective IDs. Also verify that
setuid(0) fails after dropping privileges, confirming root cannot be
re-acquired. This is a defense-in-depth hardening measure per CWE-250
and CERT POS37-C. All added syscalls (geteuid, getegid, setuid) are
async-signal-safe, so they are safe in the pre_exec context.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-05 14:27:48 -08:00
John T. MyersandJohn Myers b02bd9a0ff chore(skills): consolidate spike output into single issue (#131)
The create-spike skill was splitting findings between the issue body and
a follow-up comment. This made spike results harder to read and review.
Merge the technical investigation section into the issue body template
and remove the comment-posting step entirely.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-05 13:31:20 -08:00
Piotr Mlocek 5befdbf307 fix(sandbox): remove control plane bypass from proxy (#128)
The sandbox proxy had a control_plane_endpoints allowlist that let
sandboxed processes reach the gateway without OPA evaluation or SSRF
checks. This is a security issue: if the sandbox can talk to the
control plane, it could modify its own policy, exfiltrate data through
provider environment endpoints, or abuse other gateway APIs.

All connections now go through OPA policy evaluation and SSRF
protection with no exceptions. Removes AllowedEndpoint, parse_endpoint_url,
the is_control_plane bypass in handle_tcp_connection, and the engine field
from ConnectDecision.
2026-03-05 12:50:26 -08:00
John T. MyersandJohn Myers e9f10719f4 fix(security): harden sandbox SSH with mandatory HMAC secret, NetworkPolicy, and nonce replay detection (#127)
* fix(security): harden sandbox SSH with mandatory HMAC secret, NetworkPolicy, and nonce replay detection

Closes #25

- Make NEMOCLAW_SSH_HANDSHAKE_SECRET mandatory: server and sandbox both
  refuse to start if the secret is empty/unset. Cluster deployments
  auto-generate it via openssl rand in the entrypoint script.
- Add Kubernetes NetworkPolicy restricting sandbox port 2222 ingress to
  the gateway pod only, preventing lateral movement from other cluster
  workloads.
- Add NSSH1 nonce replay detection with a TTL-bounded cache, rejecting
  replayed handshakes within the timestamp validity window.
- Add unit tests for verify_preface (valid, replay, expired, bad HMAC,
  malformed) and env injection.

* fix(deploy): pass sshHandshakeSecret in fast deploy helm upgrade

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-05 12:21:18 -08:00
John T. MyersandJohn Myers 780731f6ba feat(e2e): parallelize e2e tests with pytest-xdist (default -n 5) (#102)
Closes #101

Add pytest-xdist for parallel e2e test execution with configurable
concurrency. Default to 5 workers; override via E2E_PARALLEL env var
(accepts a number or 'auto' for CPU-count matching). Make session-scoped
mock inference route fixtures worker-safe by incorporating the xdist
worker_id into route names and routing hints.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-05 08:04:53 -08:00
Drew Newberry f0dce007c0 feat(cli): fall back to last-used sandbox when name is omitted (#70) 2026-03-05 01:09:47 -08:00
Drew Newberry 48129c13be ci(docs): disable publish job until GitHub Pages is configured (#122) 2026-03-05 00:39:44 -08:00
Drew Newberry c077d1e9f2 ci: fix docs-build publish job and rename snapshot release to devel (#121) 2026-03-05 00:26:48 -08:00
Drew Newberry 4a78865b90 feat(ci): add CLI binary builds and snapshot release to publish workflow (#110) 2026-03-04 23:57:22 -08:00
Drew Newberry 9d088a27ce docs: reset CONTRIBUTING.md and add mise run docs task (#119) 2026-03-04 22:39:51 -08:00
Miyoung ChoiandDrew Newberry 11f795a462 docs: setup initial docs/ infrastructure and scaffolding (#94)
* set up docs

* rm nv sphinx theme version

* rm v* trigger

* incorporate feedback with cursor

* minor updates

* doc docs

* more small tweaks

* clean contributing

---------

Co-authored-by: Drew Newberry <anewberry@nvidia.com>
2026-03-04 22:31:45 -08:00
Drew Newberry 3da64744f0 feat(cli): add --from flag to sandbox create for unified image sources (#89) 2026-03-04 21:15:13 -08:00
Drew Newberry 99bba8028a feat(sandbox): support policy discovery and restrictive defaults on sandbox containers (#84) 2026-03-04 17:17:22 -08:00
Piotr Mlocek 03939e0a6c chore(ci): remove Gitlab CI config (#95) 2026-03-04 14:24:06 -08:00
Drew Newberry d920d39dd4 chore: more contributing improvements (#103) 2026-03-04 13:20:24 -08:00
Drew Newberry 90da02a7ed chore: simplify contributing workflow and documentation (#92) 2026-03-04 13:06:32 -08:00
John T. MyersandJohn Myers 05b868b935 fix(ci): replace deleted gsactions/dco-check with contributor-assistant (#98)
The gsactions/dco-check action repository was deleted, causing every DCO
workflow run to fail with startup_failure. Replace it with
contributor-assistant/github-action pinned to a commit SHA, matching the
pattern used by NVIDIA-NeMo/DataDesigner.

Signed-off-by: John Myers <johntmyers@users.noreply.github.com>
Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-04 11:11:48 -08:00
John T. MyersandJohn Myers af8fe4d1d5 refactor(policy): consolidate duplicated YAML struct hierarchies (#97)
* refactor(policy): consolidate duplicated YAML struct hierarchies into navigator-policy

Closes #96

Merge the Deserialize-only input structs and Serialize-only output structs
into a single set of types in navigator-policy that derive both Serialize
and Deserialize. This eliminates the duplicate PolicyYaml hierarchy in
navigator-cli and fixes three round-trip issues:

- filesystem_policy vs filesystem field name mismatch
- allowed_ips silently dropped on proto-to-YAML conversion
- network policy name field silently dropped on proto-to-YAML conversion

Also adds api_patterns support to the inference YAML schema and switches
network_policies from HashMap to BTreeMap for deterministic output ordering.

* fix(e2e): update non-CONNECT test assertion from 405 to 403

Align test_l4_non_connect_method_rejected with the proxy behavior
change in c06117e which intentionally returns 403 for non-CONNECT
requests.

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-04 10:35:54 -08:00
John T. MyersandJohn Myers 1d81fb187b feat(skills): create nemoclaw-cli agent skill (#85)
* feat(skills): create nemoclaw-cli agent skill and fix generate-sandbox-policy schema drift

Closes #83

Create a new nemoclaw-cli agent skill with tiered workflow guidance (basic
through advanced) and a supplementary CLI reference. The skill covers provider
management, sandbox lifecycle, policy iteration loops, BYOC pipelines, and
agent-assisted sandbox sessions. It cross-references generate-sandbox-policy
for policy content authoring and includes --help self-teaching as a fallback.

Fix critical schema drift in generate-sandbox-policy: rename
allowed_routing_hints to allowed_routes (matching the actual proto/serde
field name). Update default read_only paths to match dev-sandbox-policy.yaml.
Fix the same field name error in architecture/security-policy.md.

* policy updates

* fix(test): update OPA tests for renamed github_ssh_over_https policy key

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-04 09:52:55 -08:00
Piotr Mlocek 11f19a9176 fix(sandbox): fix data corruption in sync --down and hang in sync --up (#93)
The sandbox SSH server used tokio::spawn fire-and-forget for each stdout/stderr
chunk, which provided no ordering guarantee under tokio's work-stealing scheduler.
Under load, chunks arrived out-of-order, corrupting tar streams during download.

Replace tokio::spawn with runtime.block_on() to ensure each chunk is fully sent
before reading the next. Remove AsyncWriteExt::shutdown() from the SSH proxy's
copy helper to prevent gateways from treating TCP half-close as full-close and
truncating in-flight downloads.

Implement channel_eof handler on the sandbox SSH server to close the child
process stdin when the client signals EOF. Without this, commands like
'cat | tar xf -' used by sync --up would hang waiting for stdin EOF
indefinitely. Add ValueHint::AnyPath to the --up argument for shell completion.
2026-03-04 08:35:04 -08:00
Piotr Mlocek 4b8ca74da8 fix(cli): add path hints for file-valued flags (#86) 2026-03-03 21:40:57 -08:00
John T. MyersandJohn Myers 66df9f7614 feat(tui): add port forwarding support to Gator (#81)
* feat(tui): add port forwarding support to Gator (#80)

Extract forward PID management, resolve_ssh_gateway, and shell_escape from
navigator-cli into navigator-core::forward as a shared module. Add a Ports
field to the TUI create sandbox modal, a NOTES column to the sandbox table,
and a Forwards row to the sandbox detail view. On creation with ports, Gator
polls for Ready state then spawns background SSH tunnels. Forward cleanup
runs automatically on sandbox delete.

Closes #80

* fix(tui): fix Ports field invisible due to modal height overflow

The modal height calculation used content_height + 3 but block chrome
(borders + padding) is 4 rows, not 3. Combined with the 3-row Ports
field layout, the input line was clipped to zero height on standard
24-row terminals. Switch Ports to a compact single-line inline layout
and fix the chrome arithmetic.

* wip: BYOC example with port forwarding and TUI command field fix

- Move examples/bring-your-own-container.md into its own directory
- Add example Dockerfile and app.py (Python REST API with /hello endpoint)
- Rewrite README with full CLI and TUI port-forward workflows
- Change TUI Command field default from /bin/bash to empty so custom
  image entrypoints run without manual clearing

* feat(tui): wire up Command field to SSH exec after sandbox creation

When a command is specified in the create sandbox modal, the TUI now
waits for the sandbox to reach Ready (with the pacman animation),
starts any port forwards, then suspends the TUI and executes the
command via SSH — matching the CLI's post-creation flow.

- Refactor spawn_create_sandbox to poll for Ready inline when ports
  or command are set, keeping the animation visible throughout
- Add start_port_forwards() helper called within the create task
- Add handle_exec_command() to suspend TUI and run SSH exec
- Remove unused ForwardResult event variant (forwards are now started
  within the create task, not as a separate event)

* fix(tui): fix command exec, forward timeouts, and create modal UX

- Fix handle_exec_command to suspend TUI and run SSH attached (matching
  CLI behavior) so the process stays alive for the session duration
- Fix shell_escape double-quoting: escape each word individually
- Add ConnectTimeout and 20s spawn timeout to forward SSH so a stalled
  auth doesn't freeze the create flow forever
- Remove dead ForwardResult event variant
- Add cluster_name to start_port_forwards for ProxyCommand
- Add spacer between Providers and Ports in create modal
- Fix Command placeholder: 'runs /bin/bash if empty' (not image entrypoint)
- Update BYOC README: document that CMD is replaced by supervisor,
  command must be passed explicitly, remove TUI-specific sections

* fix(sandbox): demote Landlock fallback log from warn to debug

The Landlock filesystem sandbox emits a noisy warning when a policy
path does not exist (e.g. /app in custom images). Since BestEffort
mode intentionally continues without Landlock, demote to debug.

* docs: fix BYOC Dockerfile comments for supervisor CMD override

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-03 17:31:44 -08:00
John T. MyersandJohn Myers c06117eb15 fix(proxy): return 403 for non-CONNECT requests, add deny logging, and revise error messages (#79)
Closes #42

- Change non-CONNECT proxy response from 405 to 403 to align with
  how CONNECT denials are surfaced
- Add structured deny logging for non-CONNECT requests with hostname
  extraction from absolute-form URIs
- Revise 7 user-facing error messages across proxy.rs and
  dev-sandbox-policy.rego to follow consistent principle: generic
  policy-deny messages for non-inference requests, descriptive messages
  for recognized inference endpoints
- Update E2E test assertion to match new error message
- Update architecture docs to reflect new behavior

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-03 13:06:17 -08:00
Drew Newberry 9099bc3972 chore: rename Navigator to NemoClaw across user facing contracts (#73) 2026-03-03 11:41:19 -08:00
Drew Newberry 46381e65cf fix(ci): harden cargo build retry by wiping target dir and disabling sccache (#77)
The multi-arch publish job fails with 'extern location does not exist'
errors for version_check when BuildKit cache mounts contain stale
artifacts. The existing retry logic (cargo clean + retry) was
ineffective because:

1. cargo clean relies on its own metadata to decide what to remove,
   which can itself be stale on BuildKit cache mounts shared across
   different build profiles (debug vs release).

2. sccache remained enabled during the retry, so a corrupt memcached
   cache entry would reproduce the same failure even after cleaning.

Replace cargo clean with rm -rf /build/target/* for a thorough wipe,
and unset RUSTC_WRAPPER on retry so cargo falls back to raw rustc.
2026-03-03 11:32:07 -08:00