feat(minimal): guest fetch and degraded-mode fallback for min bug - #880
Conversation
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 42 minutes 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 ignored due to path filters (1)
📒 Files selected for processing (9)
Comment |
|
Two fixes pushed to 1. The manifest asserted the daemon logs were absent while carrying them
…with 178 KB of daemon logs sitting on the volume the whole time. Once Unit 7 lands, the same This is a regression, not a longstanding gap: the reference implementation (#784) emitted two skips, neither about logs. Unit 2 (#816) introduced this one on a temporal rationale — "the files only begin to exist after Unit 3" — which never considered that on macOS the host will never have them. What changedThe claim was never wrong about the host; it was wrong about the bundle. Whether it is safe to make is not knowable when the log collector runs —
Plain absence is now claimed only where it holds of the bundle: for a host-only writer ( and with a bundle nested, the same skip ends The test is the actual fix
One level down, untouched: 2. Nothing told a reader which log was whichAfter Unit 7 a bundle carries two overlapping copies of the daemon's output —
Why the file and not a
|
| Command | Result |
|---|---|
cargo build -p minimal (native macOS) |
clean |
cargo clippy -p minimal --lib --no-deps -- -D warnings |
clean |
cargo fmt -p minimal --check |
clean |
min bug against a throwaway --minimal-dir (no live daemon) |
21 entries, 0 errors; both skip reasons and logs/PROVENANCE.txt verified in the manifest |
cargo clippy -p minimal --all-targets |
cannot run here — pre-existing: the minimald dev-dep pulls hakoniwa → libcgroups → procfs, whose build script hard-fails off-Linux (verified again on this branch) |
cargo test -p minimal — the two new tests |
cross test -p minimal --lib --target aarch64-unknown-linux-musl run (the recipe in the PR body, HOME=/tmp/xhome, CARGO_BUILD_JOBS=1) was still compiling dependencies after ~25 min against a cold musl target dir and was abandoned, not failed. Please treat both new tests as unrun until the Linux lane reports. |
So: the code paths are verified natively through the real binary, and the test assertions over them are not. The one branch neither covers is DaemonLogSources::Nested end to end — the unit test drives it with a synthesised daemon bundle rather than a real fetch. Explicitly not run: anything needing a live microVM; artifact 4 in the PR body (kill a real VM's daemon mid-session) is still unverified and this does not change that.
Likely conflict ahead: another change in flight adds a --log-tail-bytes flag off main, touching crates/minimal/src/diag/. It will almost certainly want to plumb a cap through collect::logs, whose signature I just changed (added the out-param) and whose body I edited around LOG_TAIL_CAP. Same function, same lines — whoever merges second should expect to resolve it by hand.
|
@macroscope review |
|
Manual reviews triggered for commit All prior checks · these links stay valid even if you push more commits. |
|
Just FYI for future @mentions, I'm Review triggered. Results will be posted as check runs when complete. |
ApprovabilityVerdict: Needs human review 2 blocking correctness issues found. This PR introduces new guest bundle fetching and degraded-mode fallback capabilities with substantial new logic. Two unresolved review comments identify potential correctness issues in error handling that should be addressed before merging. You can customize Macroscope's approvability policy. Learn more. |
Review round worked — 2 open findings closed, 8 confirmed already closed, 1 adjacent defect fixedFive commits on
VerdictsOpen at the start of this pass — both valid, both fixed.
…in a manifest that also carried two
Already closed before this pass — verified present in the tree at One change that is not from this round
ENOENT on exec, not on the image — on the platform where minimald runs inside the microVM and the volume is the only place its logs exist. Separated into its own commit so it can be dropped independently if it is not wanted here. Pre-existing, deliberately not fixed
Verification
The two tests added in alongside the four added here: The three guest-fetch integration tests all pass ( so
Merge conflict expected#898 ( |
Per provider, `min bug` now performs the staged socket probe and — when the probe handshakes — downloads the daemon's own bundle over the DiagBundleTarZst subsystem, nesting it under providers/<name>/guest/. --no-guest skips daemon contact entirely; --guest-timeout-secs bounds each provider's download. Host-side log-prefix skips are deferred until the provider loop settles whether the daemon's logs reached the bundle another way, so the manifest never claims an absence the archive does not back. Squashed rebuild of the original branch onto main after #878 landed there, replacing the merge-heavy history whose #889 squash title also failed commitlint. Refs: #802 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR #895 changed the serve! dispatch so every handler returns Result<(), ConnectionError> for outcome logging; PR #878 merged alongside it with serve_stream_diag_bundle still returning (). The two were each green against a main that lacked the other, and the type mismatch only surfaced on branches built after both landed. Failure still relays the message over the channel's extended-data stream before surfacing as ConnectionError::Internal, mirroring serve_stream_workspace_files. Refs: #878, #895 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
f5f07f8 to
6fa59da
Compare
Unit 7 of the diagnostics series (spec: #802, epic: #801).
Important
Stacked on #878 (Unit 6) and must merge after it. The base of this PR is
feat/diag-unit6-daemon-bundle, notmain— Unit 7 consumes Unit 6'sDIAG_BUNDLE_SUBSYSTEM/DiagBundleRequestand the manifest-bearing guest bundle. Review the diff against that base; if GitHub shows Unit 6's changes too, Unit 6 has not merged yet.What
min bugnow reaches into every provider: a staged socket probe that says exactly where daemon contact breaks, the bundle download over the probe's own connection, and — when the daemon or transport is the suspect — vital signs and logs harvested read-only from the volume image. All of it iscrates/minimalonly;minimald/minimald-rpcare untouched.client.rs(R7.2) —Client::download_diag_bundle: writes oneDiagBundleRequest, half-closes, and accumulates the streamed tar.zst up to a 256 MiB cap, returning(bytes, truncated). Daemon errors ride extended-data stream 1 (capped at 64 KiB) and become theErr, discarding any partial bundle; a bare subsystem refusal (Failure) fails fast rather than blocking until the caller's deadline. Sends the traceparent so the download joins the CLI's trace.diag/net.rs(R7.1) — theSocketProbe:stat→connect→ SSHhandshake→GetVersion, each a prerequisite for the next, recorded asproviders/<name>/socket-probe.json. The failing stage is the diagnosis (no socket file / stale socket / wedged-behind-the-bridge / up-but-unhealthy). On a completed handshake it hands the liveClientback so the download reuses the connection — no second handshake.diag/guest.rs—collectdownloads and nests the daemon bundle raw atproviders/<name>/guest/daemon-diag.tar.zst(R7.3);volume_fallbackis the degraded path (R7.5);record_skippedwrites the per-provider skip note (R7.4).diag/collect.rs(R7.6) —provider_files: per-providerdir-listing.txt, verbatimminvmd.toml, andstatus.json(raw lifecycle + non-mutatingminvmd/minimaldliveness viaStateDir, deliberately noteffective_state()which repairs stale state by writing it back).diag/mod.rs— the real provider loop replaces the Unit 2 skip, plus--no-guestand--guest-timeout-secs(default 60).R7.3 bounded decode (the one that has to be right)
The nested daemon bundle is stored raw either way; verification only decides whether the manifest records it as trustworthy.
verify_nested_bundledrains the decompressed stream into a sink — bytes are counted and dropped, never materialized — and returns the instant a cap trips, so it never skips past a bomb's body. Three caps, each independent:manifest.jsonrequired (a well-formed daemon bundle always ends with one; absence means the stream is not what it claims).A breach or decode failure still
add_bytes-es the raw blob, then records aguest.<name>.verifymanifest error naming the failed check (size check:/entry-count check:/manifest check:/decode check:).Warning
Deviation from R7.3's expansion ratio. The spec writes "decompressed size capped at 4× the compressed size with a 1 GiB hard ceiling." A real diagnostic bundle is almost entirely compressible text (proc tables, JSON, logs); a representative host bundle measured 15.4× (586 KB content / 38 KB compressed), and the daemon bundle is the same shape. A literal 4× budget would flag every legitimate nested bundle as a bomb — the integration test asserts a healthy fetch verifies with zero
guest.*errors, which a strict 4× would break. The implementation keeps the ratio but clamps the budget to[64 MiB, 1 GiB], so the two hard caps the bomb guard actually rests on (the 1 GiB ceiling and the 10k-entry cap) are exactly as specified, while the ratio no longer false-positives on real bundles. TheNESTED_MIN_BUDGETfloor and its rationale are documented at the constant. Flagging rather than silently shipping — happy to swap the floor value or revisit if the ratio was meant literally.Porting deltas against the reference (
77fc711e)errors.json→manifest.jsonconvergence, so its checks would have keyed onerrors.json).net.rsmixed the host network collectors (listening tables, interfaces, routes) with the probe. Those collectors merged intodiagnostics::netin Unit 5, so only theSocketProbehalf lands here;mask_macsand the interface/route collectors are not re-added.provider_filesdoes not re-collectrun.log/boot.log— the merged tree'scollect::logsalready tail-caps them for every discovered provider, so a second copy would be a duplicate archive entry. (The reference collected them in both places, harmless only because itslogscollector did not.)DiagBundleRequestis constructed via::default()(it is#[non_exhaustive]; a cross-crate struct literal will not compile).tempfilepromotes dev-dep → main dep (volume-harvest staging);tokio-streamis added for iterating the nested archive during verification;mod common;(the daemon harness) enterstests/bug.rs— all three per the spec's baseline notes.Proof Artifacts
min bugagainst the harness daemon nests a guest bundle whose manifest parses; loadout redaction and client-key skip hold across layers — R7.1–R7.3bug_with_daemon_nests_a_verified_guest_bundlebug_with_stale_socket_reports_the_connect_stage_and_falls_back--no-guestagainst a live harness daemon performs zero daemon contact — R7.4bug_no_guest_makes_no_daemon_contactmin bug: probe stages +volume-meta.json+ harvestedvolume-logs/present — R7.5 end to endvolume-meta.jsonwritten,debugfspath taken), but the live end-to-end gate against a real ext4 image has not been run.The R7.3 caps have dedicated unit tests in
diag::guest::tests:a_well_formed_bundle_verifies,a_bundle_without_a_manifest_fails_the_manifest_check,a_decompression_bomb_fails_the_size_check(a 72 MiB zero run that zstd squeezes below the budget floor),too_many_entries_fails_the_entry_check(10,001 members), anda_truncated_stream_fails_the_decode_check.Verification
crates/minimal's test suite is Linux-only —minimaldis a dev-dependency and pullsprocfs, socargo test -p minimaldoes not build on the macOS dev host (pre-existing, not introduced here). It was verified viacross, not natively:cargo build -p minimal(native macOS)cross clippy -p minimal --all-targets --target … -- -D warningscross test -p minimal --target …— libcross test -p minimal --target …—tests/bug.rsBoth
crossinvocations ran withCROSS_CONTAINER_OPTS="--env HOME=/tmp/xhome"; without a writableHOMEsome tests failPermissionDeniedon/.cache.CARGO_BUILD_JOBS=1is also needed on this host — linkingbin "min"andtest "cli"concurrently OOM-killsldinside Docker's 7.7 GiB VM (collect2: fatal error: ld terminated with signal 9), which looks like a compile failure but is not one.cargo clippy -p minimal --libis not usable natively — it trips pre-existing macOS-cfg dead-code errors incrates/sandbox2(needs_lib_symlink,network_namespaces_available, …) unrelated to this change — hence thecrossclippy run for the real signal.The 2
tests/bug.rsfailures are pre-existing, not from this PRincident_collectors_land_and_mask_macsandlogs_collects_newest_five_per_prefix_and_provider_logsare Unit 5 tests (#864). This branch does not modify either — the only deletions intests/bug.rsare four doc-comment lines. They fail because the cross musl image ships none ofip,ifconfig,ss,netstat,lsof(verified by inspecting the image directly), sohost/net/interfaces.txtcannot be produced and the manifest carries 2 collector errors against anassert_eq!(errors, 0). CI's Linux lane is the authoritative environment for these two; they are called out here rather than left for a reviewer to trip over.Two lint failures that were ours — fixed in
f6c8a54aThe first
--all-targets -D warningsrun over this series caught both:manual_clampon the nested-bundle budget — the.max().min()pair is a clamp. The floor is a compile-time constant below the ceiling, so the rewrite cannot introduce the panic clippy warns about.dead_codeoncommon::TestDaemon::server— addingmod common;totests/bug.rsgives that binary its own copy of the module, and it does not read the fieldtests/cli.rsdoes.expectwould go unfulfilled in theclibinary, soallowis the accurate annotation.Refs: #801
🤖 Generated with Claude Code
Note
Add guest bundle fetch and degraded-mode fallback to
min bugdiagnostic commandClient::download_diag_bundlein client.rs to stream a tar+zstd diagnostic bundle from the daemon over SSH, returning collected bytes and a truncated flag with bounded error capture./logsfrom the ext4 volume viadebugfswhen the daemon is unreachable.socket-probe.jsonin the bundle.explain_absent_log_prefixes.--no-guestand--guest-timeout-secs(default 60s) CLI flags;tempfileandtokio-streamare promoted from dev-only to runtime dependencies.Macroscope summarized 10c7830.