ci: add the Linux-native lane, retire the mothballed netns lane - #697
Conversation
New always-triggered ci-linux-native.yml — the "Linux with native minimald" target lane (#687), the first lane born on the changes-filter + aggregator pattern (path scoping INSIDE the workflow; the if: always() aggregator reports success on path-skipped runs, so the context can become a required check without the ci-docs-skip.yml inverse-path hack): - tests: the core-tests composite (nextest workspace + doctests), taking over ci.yml's test job for this lane. ci.yml keeps its copy until the ruleset requires ci-linux-native-success. The networking-proxy/networking-wg feature steps are mothballed by owner decision, not moved. - native-daemon-e2e: scripts/native-daemon-e2e.sh boots the NATIVE minimald on the host (DM2) via `minimal activate` autospawn, creates a session for the repo's own project, execs a command in the sandbox via `minimal attach --command` asserting stdout + exit status, then list/destroy/stop — with fresh XDG state/runtime dirs for a true cold start, the package cache carried across runs via actions/cache, and a diagnostics dump on failure. Before this job nothing in CI ran the daemon natively. E2E_ACTIVATE_ARGS is the hook for `--loadout dev` once the loadouts CLI lands (#686). Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Delete ci-netns.yml per the networking-mothball decision (#687): the netns UC1/UC6 proofs and the mesh UC7 proof stay in-tree and runnable locally but are no longer CI-covered. Update the test files' doc comments and #[ignore] reasons, plus the switch.rs comment, so they no longer point at a workflow that does not exist and instead say how to run the proofs locally. Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 53 seconds Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds a filtered Linux-native CI workflow, a native daemon lifecycle E2E script, and an aggregator job. The obsolete netns workflow is removed, and related implementation and test documentation is updated to describe mothballed local execution. ChangesNative Linux CI
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
First live run proved the daemon path works (autospawn, session
create, listing all succeeded) but the script's sid capture broke:
the CLI's tracing layer writes to STDOUT (ot::StdoutWriter), so the
autospawn INFO lines interleaved with the session id `activate`
prints, and the ANSI-laden multi-line capture then blew up grep
("Invalid range end").
- RUST_LOG=warn (overridable) quiets the info logs for the e2e run
- the session id is the LAST stdout line, validated as a UUID before
use, with the full stdout dumped when it isn't
- id matching uses grep -Fqx (fixed string), immune to stray
metacharacters either way
Logs-on-stdout itself is worth a separate look: `ls --raw` exists
"for piping into scripts", and any autospawn log breaks such pipes
at default verbosity.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
scripts/native-daemon-e2e.sh (1)
125-126: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win"Must not survive" is not actually verified.
The comment claims the daemon must not survive
minimal stop, but the script only checks the exit code ofminimal stopitself — it never confirms the daemon process/socket is actually gone (e.g., a subsequentminimal lsfailing, or the socket file disappearing). A daemon that exits 0 onstopbut lingers (or a stop that no-ops when nothing was listening) would pass silently.♻️ Suggested strengthening
# Shut the daemon down; it must not survive. minimal stop >/dev/null 2>&1 || { echo "::error::'minimal stop' failed"; fail; } +if minimal ls >/dev/null 2>&1; then + echo "::error::daemon still reachable after 'minimal stop'"; fail +fi🤖 Prompt for 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. In `@scripts/native-daemon-e2e.sh` around lines 125 - 126, Strengthen the shutdown verification around the `minimal stop` command: after it succeeds, explicitly confirm the daemon is no longer running by checking its socket/process state, such as asserting a subsequent `minimal ls` fails or the socket disappears. Call `fail` with a clear error if the daemon remains available, while preserving the existing failure handling for `minimal stop`.
🤖 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.
Nitpick comments:
In `@scripts/native-daemon-e2e.sh`:
- Around line 125-126: Strengthen the shutdown verification around the `minimal
stop` command: after it succeeds, explicitly confirm the daemon is no longer
running by checking its socket/process state, such as asserting a subsequent
`minimal ls` fails or the socket disappears. Call `fail` with a clear error if
the daemon remains available, while preserving the existing failure handling for
`minimal stop`.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 908b554e-891e-43cf-98f4-5e0b82958f89
📒 Files selected for processing (6)
.github/workflows/ci-linux-native.yml.github/workflows/ci-netns.ymlcrates/minimald/src/net/switch.rscrates/minimald/tests/mesh_uc7.rscrates/minimald/tests/netns.rsscripts/native-daemon-e2e.sh
💤 Files with no reviewable changes (1)
- .github/workflows/ci-netns.yml
Generalize the native lane's daemon e2e into scripts/session-e2e.sh, the SAME proof for all three deployment targets — the CLI already abstracts where the daemon lives, so the script only needs knobs: Linux native (DM2) no knobs (host daemon, repo as project) Linux KVM (DM1) E2E_VM=1 E2E_MINIMAL_ARGS=--minvmd macOS HVF E2E_VM=1 (macOS is always VM-backed) Flow everywhere: clean-state `minimal activate` (the cold activate IS the autospawn) -> ls --raw -> `attach --command` exec asserting stdout + exit (sessions are Linux environments on every target, so `uname -s` == Linux even on macOS hosts) -> warm call -> destroy -> stop. Timing reported, not asserted. VM targets get minvmd teardown and guest-boot-log diagnostics. E2E_PROJECT_DIR parameterizes the project: /tmp on VM targets — the guest sees no host project dir until project sync lands, and /tmp exists in the guest image. Portability handled: /tmp-rooted state dirs (macOS sun_path 104), perl ms-clock fallback (BSD date has no %N). The mac lane's autospawn step is retired: it only proved `minimal ls` cold/warm against the spawned VM, which the unified e2e's cold activate subsumes — and the CLI's macOS session path (activate / attach / destroy over the vsock bridge) gets covered for the first time. The native lane invokes the same script unchanged in behavior. The KVM lane's adoption (E2E_MINIMAL_ARGS=--minvmd) follows once the build/test split (#699) lands, to avoid cross-PR conflicts on that file. Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Scope addition (owner-requested): one session e2e for every target, and the mac autospawn test retired — commit 0d385ec.
The mac autospawn step is deleted: its 🤖 Generated with Claude Code |
Un-mothball the UC1 (no-net), UC4 (static ingress), and UC6 (PTask<->PTask over the gvproxy switch) proofs in crates/minimald/tests/netns.rs, which lost their CI lane when ci-netns.yml was retired (#697). They drive the production switch-attach wiring against a real gvproxy switch and need only unprivileged userns + sudo (no KVM), so they run on a stock ubuntu-latest runner. - Add .github/workflows/ci-netns.yml using the current always-triggered + in-workflow paths-filter + success-aggregator lane pattern (no trigger-level `paths:`). Drops the WireGuard mesh (mesh_uc7) step — that `networking-wg` feature is disabled for now. - Update the netns.rs header and #[ignore] reasons (no longer mothballed). - Correct the stale "mothballed netns" notes in ci-linux-native.yml. The three proofs pass locally on this branch (MINIMALD_NETNS_TEST=1 GVPROXY_BIN=... cargo test -p minimald --test netns -- --include-ignored), confirming the new switch-side ingress gate does not regress UC1/UC4/UC6. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Un-mothball the UC1 (no-net), UC4 (static ingress), and UC6 (PTask<->PTask over the gvproxy switch) proofs in crates/minimald/tests/netns.rs, which lost their CI coverage when the standalone ci-netns.yml was retired (#697). They are Linux-native (DM2) integration tests that need only unprivileged userns + sudo and a userspace gvproxy switch (no KVM), so they run as a `netns-integration` job in the existing ci-linux-native lane rather than a separate workflow / required check. - Add the `netns-integration` job to ci-linux-native.yml (enable userns, fetch the pinned gvproxy, run `cargo test -p minimald --test netns -- --include-ignored`); wire it into the lane's success aggregator and add the gvproxy pin to the changes filter. - Un-mothball netns.rs: update the header and #[ignore] reasons. The WireGuard mesh proof (mesh_uc7, networking-wg) stays out — that feature is disabled. The three proofs pass locally on this branch, confirming the new switch-side ingress gate does not regress UC1/UC4/UC6. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(minimald): frame gvproxy forwarder replies by Content-Length
The forwarder control verbs (POST /services/forwarder/expose and the
:7654/:7655 proxy-publish) sent HTTP/1.0 with `Connection: close` and
read the reply to EOF. Over the KVM libkrun `add_vsock_port2(listen =
false)` shuttle, gvproxy's response is dropped when it closes the socket,
so the guest reads 0 bytes ("malformed gvproxy status line") even though
the forward was applied. apply_ingress then rolls back a *successful*
own-ip ingress attach, so the session's shell never starts (gap G-N8);
own-ip without ingress is unaffected.
Send HTTP/1.1 keep-alive (no `Connection: close`) and frame the reply by
Content-Length instead of read-to-EOF, so the server never closes first
and the response drains to the guest, which closes from its own side.
This also repairs the in-VM :7654/:7655 proxy-publish (same leg). The
native unix path (DM2) is unaffected.
Verified live on DM3 (KVM): own-ip + --ingress attach starts the shell
and publishes the lease; TC3/TC4/TC7/TC8 = 200/200/(401+200)/200, with
no startup proxy-publish warnings. Adds a regression test that hangs if
the framing reverts to read-to-EOF.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(minimald): enforce session-to-session ingress and register PTask DNS
Two networking findings from a cross-platform (DM1/DM2) test run:
- #2: switch-side ingress was unenforced. `--ingress` only gated the
host-loopback publish, so a peer session or the daemon tap could reach
any port on an own-IP PTask over the shared gvproxy switch (gvproxy has
no per-client ACL API). Add a stateless TCP-SYN gate to the inbound
relay leg (`relay_switch_to_tap`): a bare SYN (SYN set, ACK clear) to a
port not in the target's ingress policy is dropped; established/return
traffic, egress, and declared ports pass. UDP and non-IPv4 pass through
(documented limitation). Wires the previously-dead `PolicyWarnLimiter`
as the R2.7 drop-log site.
- #3 / UC6: a session could not resolve a peer's `*.min.internal` name,
and the own-IP lease rotates per attach. Register `<session>.<host-id>`
to the current lease in gvproxy's runtime DNS (POST /services/dns/add)
on every own-IP attach. gvproxy's resolver is the switch gateway every
sandbox already queries, so no new resolver or resolv.conf change is
needed; gvproxy's newest-wins merge picks up a rotated lease with no
remove verb.
Peers can now reach declared ports on another session by hostname (UC6),
subject to that target's ingress (#2). Verified end-to-end on DM2 (the
name registers to the live lease) plus unit tests for the frame parser
and the DNS request shape.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* build(minimal,minimald): disable ssh-forward and WireGuard mesh
Gate the SSH port-forward and WireGuard-mesh surfaces behind new,
off-by-default features so they compile out for now: UC7, UC2b option A,
and the ssh-forward fallback are deferred (the daemon does not yet consume
mesh enrolment, and ssh-forward's target resolves in the VM/host context
rather than the sandbox).
- New `remote-access` feature on `minimal` gates the `mesh` and
`ssh-forward` (alias `forward`) subcommands and their plumbing.
- New `ssh-forward` feature on `minimald` gates the `direct-tcpip`
handler; with it off, a `#[cfg(not)]` stub rejects every forward so the
daemon fails closed rather than relying on russh's default handler.
- Drop `networking-wg` from the built features (justfile) and remove the
WireGuard mesh test steps from CI and release. The mTLS reverse proxy
(`networking-proxy`) is unaffected and stays enabled.
Both crates compile, clippy, and test clean with and without the new
features.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(networking): remove the superseded CLI test-plan
The `test-plan.md` / `test-plan.sh` pair under `03-spec-networking/`
described an earlier DM1/DM2 CLI walk-through and is no longer maintained;
nothing in the build, CI, or code references it. The networking spec,
architecture, and diagram docs remain.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(minimald): enforce UDP ingress with per-PTask conntrack
Extends the switch-side ingress gate (finding #2) to UDP, which the
initial TCP-SYN gate left as a documented gap. UDP has no
connection-establishment signal, so a stateless port check cannot tell a
solicited reply (a DNS/QUIC response to an ephemeral port) from an
unsolicited datagram.
Add a per-PTask `UdpConntrack`: the egress relay leg records each outbound
datagram's reverse flow `(remote_ip, remote_port, local_port)`, and the
ingress leg allows a matching reply while dropping UDP to an undeclared
port that matches no flow. Declared UDP ingress ports (the internal port
of a UDP port-mapping) are always allowed. One flow table is shared
between the two relay tasks; entries expire after 120s with an
opportunistic sweep bounding the table.
Refactors the frame parse into `parse_ipv4_l4`, reused by the TCP and UDP
predicates. This closes UC6 for UDP round-trips (a peer reaching a
declared UDP port, and its reply, both work) and completes spec Req 6's
"TCP and UDP subject to target ingress."
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci(netns): run the network-namespace proofs in the linux-native lane
Un-mothball the UC1 (no-net), UC4 (static ingress), and UC6 (PTask<->PTask
over the gvproxy switch) proofs in crates/minimald/tests/netns.rs, which lost
their CI coverage when the standalone ci-netns.yml was retired (#697). They
are Linux-native (DM2) integration tests that need only unprivileged userns +
sudo and a userspace gvproxy switch (no KVM), so they run as a
`netns-integration` job in the existing ci-linux-native lane rather than a
separate workflow / required check.
- Add the `netns-integration` job to ci-linux-native.yml (enable userns,
fetch the pinned gvproxy, run `cargo test -p minimald --test netns
-- --include-ignored`); wire it into the lane's success aggregator and add
the gvproxy pin to the changes filter.
- Un-mothball netns.rs: update the header and #[ignore] reasons.
The WireGuard mesh proof (mesh_uc7, networking-wg) stays out — that feature is
disabled. The three proofs pass locally on this branch, confirming the new
switch-side ingress gate does not regress UC1/UC4/UC6.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(minimald): remove dead networking code
Drop three unreferenced networking items found by a workspace-wide
dead-code sweep. All are behaviour-neutral: an identical before/after
run of the native-daemon and KVM-VM networking test plans produced the
same results (in-sandbox isolation, host egress, own-IP egress, managed
DNS proxy, static ingress, and the mTLS reverse proxy all unchanged),
and the network-namespace enforcement proofs stayed green.
- guest.rs: a no-op `let _ = &cidr;` — `cidr` is already consumed by the
"egress up" tracing call a few lines below, so the borrow-and-drop did
nothing.
- session_host.rs / session.rs: the `SandboxLauncher::session` field (and
its unjustified `#[allow(dead_code)]`) was constructed but never read
and has no Drop side effect; remove it with the now-unused import, the
struct initializer, and rename the launcher parameter to `_session`.
- net/mod.rs: the `GATEWAY_MAC` re-export had no consumer anywhere in the
workspace (the switch crate uses its own constant internally).
clippy --workspace --all-targets -D warnings clean; minimald unit tests
pass; ssh-forward / networking-proxy / remote-access gated builds pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JTLRizuWQ3GuZmBBv27Nt7
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
PR4 of the CI refactor (#687). Off
main— independent of the mac stack (#694/#696).New
ci-linux-native.yml— the "Linux with native minimald" target laneBorn in final form on the pattern that eventually replaces the
ci-docs-skip.ymlinverse-path hack: always-triggered, achangesjob (dorny/paths-filter, SHA-pinnedv3.0.2,pull-requests: read) gates the expensive jobs, and anif: always()aggregator (ci-linux-native-success) fails only onfailure/cancelled— so path-skipped runs still report, and the context can safely become a required check at the ruleset flip.tests: thecore-testscomposite (nextest workspace + doctests), taking overci.yml'stestjob for this lane (ci.yml keeps its copy until the ruleset flip, then drops it — PR8). The--features networking-proxy/networking-wgsteps are mothballed by owner decision, documented in-workflow.native-daemon-e2e— the genuinely new coverage: nothing in CI ran minimald natively before.scripts/native-daemon-e2e.shdrives the real user path on the host (DM2, no VM):XDG_STATE_HOME/XDG_RUNTIME_DIR→ guaranteed cold start;minimal activate .auto-spawnsminimald run --detachand creates a session for the repo's own project (the known-good fixture);minimal attach <sid> --command 'echo <marker> && uname -s'— the non-interactive exec across CLI → UDS → daemon → sandbox, asserting stdout marker + exit 0 (--attachitself chains into interactive PTY ssh, so the e2e uses activate +attach --command;E2E_ACTIVATE_ARGSis the drop-in hook for--loadout devonce #686 lands);minimal ls --rawlisting, warm-call check,destroy(verified delisted), cleanstop.~/.cache/minimal, rust stack + socat pulls) ridesactions/cachekeyed on.minimal/minimal.toml— the e2e script deliberately resets state/runtime dirs but notXDG_CACHE_HOME.stop --force.Retire
ci-netns.yml(mothballed)Deleted per the networking-mothball decision. The netns UC1/UC6 and mesh UC7 proofs stay in-tree and runnable locally; their doc comments and
#[ignore]reasons (plus aswitch.rscomment) now say that — and how to run them — instead of pointing at a workflow that no longer exists.Verification
actionlintclean on the new workflow;shellcheckclean on the script;cargo fmt --checkclean on the comment-only Rust edits.tests,native-daemon-e2e, and the aggregator all run here. Reviewers: the interesting log isnative-daemon-e2e— cold activate timing, the in-sandbox exec output, and clean destroy/stop.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Changes