Skip to content

feat(minimald,minvmd): include SSH host key in the ready beacon - #582

Merged
norrietaylor merged 7 commits into
mainfrom
sdd/467-ssh-host-key-in-beacon-a3265025597e34bc
Jul 7, 2026
Merged

feat(minimald,minvmd): include SSH host key in the ready beacon#582
norrietaylor merged 7 commits into
mainfrom
sdd/467-ssh-host-key-in-beacon-a3265025597e34bc

Conversation

@gominimal-aw-bot

@gominimal-aw-bot gominimal-aw-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

[sdd-fastpath: tracking=467 tier=sonnet]

Extends the minvmd ready-beacon protocol from one line (READY\n) to two lines (READY\n<openssh-pubkey>\n), so the host pre-populates known_hosts before the first SSH connection and eliminates the TOFU prompt.

Changes

Guest side (minimald)

  • write_ready_beacon(writer, pubkey) — testable core that formats the two-line payload
  • emit_ready_marker now accepts &PublicKey and calls write_ready_beacon
  • emit_simple_ready_marker added for the degraded READY-only fallback path (rootfs mount failure — no SSH server, no key to advertise)
  • main.rs: load host_private_key once, reuse for both known_hosts write and the vsock beacon (R1.2 — no redundant disk read)

Host side (minvmd)

  • read_ready_beacon(reader, known_hosts_path) in cmd/mod.rs: reads the second line, parses it as a PublicKey, writes the known_hosts entry via learn_known_hosts_path; key-parse failures are logged as warnings and never abort boot (R2.3)
  • default_vm_known_hosts_path() helper derives $XDG_STATE_HOME/minimal/providers/local-0/known_hosts
  • boot.rs and run.rs: duplicated inline READY-reader replaced with the shared helper
  • russh promoted from [dev-dependencies] to [dependencies] (R2.5)

Proof artifacts

Test (minimald) — R1.1: beacon writes READY\n<pubkey>\n

test guest::tests::write_ready_beacon_formats_two_lines ... ok

Constructs a generated Ed25519 key, calls write_ready_beacon with an in-memory duplex writer, asserts output equals READY\n<openssh-pubkey>\n. Fails against the old single-line implementation.

Tests (minvmd) — R2.1–R2.4: READY-marker read path writes known_hosts

test cmd::beacon_tests::read_ready_beacon_writes_known_hosts_entry ... ok
test cmd::beacon_tests::read_ready_beacon_tolerates_missing_pubkey_line ... ok
test cmd::beacon_tests::read_ready_beacon_rejects_wrong_marker ... ok
  • read_ready_beacon_writes_known_hosts_entry: writes a two-line beacon to a Cursor, calls read_ready_beacon, asserts the temp known_hosts file contains local-0.
  • read_ready_beacon_tolerates_missing_pubkey_line: single-line beacon → known_hosts not written, Ok(()) returned (backward compat with older guest).
  • read_ready_beacon_rejects_wrong_marker: first line not READYErr returned.

All new tests fail against the previous single-line implementation.

Verification

cargo test -p minimald  # 70 passed
cargo test -p minvmd    # all passed (including 3 new beacon tests)
cargo fmt && cargo test -- --include-ignored  # exit 0
cargo clippy --allow-dirty --fix --all-targets -- -D warnings  # exit 0

Merging this pull request advances the tracking issue from sdd:in-progress to sdd:done; a human does the final close.

References #467

Generated by sdd-execute (sonnet tier) ·

Summary by CodeRabbit

  • New Features
    • VM startup readiness now supports an optional second line containing an OpenSSH public key, improving automatic host-key learning.
    • Added configurable readiness timeout and VM RAM sizing via environment variables.
    • Added support for bringing up guest root egress and a new --gvproxy-bin option for proxy setup.
  • Bug Fixes
    • Improved READY-beacon parsing/validation and timeout messaging across boot/run flows.
    • Enhanced init-root transition behavior for better interactive terminal/PTY behavior.
  • Tests
    • Added unit tests covering correct/incorrect beacon formats and known-hosts updates.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR extends READY signaling to carry an optional OpenSSH host key, updates host-side parsing to learn that key, and adds guest rootfs and egress setup plus new startup wiring for gvproxy and timeout/config handling.

Changes

READY beacon, boot, and egress wiring

Layer / File(s) Summary
Guest beacon emission
crates/minimald/src/guest.rs, crates/minimald/src/main.rs
emit_ready_marker now sends a keyed two-line READY beacon when given a host public key, emit_simple_ready_marker preserves the one-line fallback, and the startup path passes the host key into the keyed emitter.
Guest rootfs and egress
crates/minimald/src/guest.rs
The initramfs transition adds /tmp, devpts, and mount/chroot sequencing updates, and bring_up_root_egress configures the guest tap, routes, resolver, and gvproxy relay.
Minimald startup wiring
crates/minimald/src/main.rs
Startup now threads gvproxy_bin, installs the rustls ring provider for networking-proxy, reuses the host private key, and attempts guest egress after READY emission.
Minvmd READY contract
crates/minvmd/Cargo.toml, crates/minvmd/src/cmd/mod.rs, crates/minvmd/src/cmd/boot.rs, crates/minvmd/src/cmd/run.rs
russh becomes a runtime dependency, READY timeout and networking env helpers are added, READY beacon parsing learns OpenSSH keys into known_hosts, and boot/run both switch to the shared beacon reader.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

Suggested labels: needs-human

Suggested reviewers: norrietaylor, twitchyliquid64

Poem

I hopped along with key in tow,
And whispered READY down below.
The host now learns my ssh song,
While tunnels and routes hum right along.
🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: adding the SSH host key to the ready beacon across minimald and minvmd.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
crates/minimald/src/guest.rs (1)

82-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider deduplicating the connect/retry loop shared with emit_ready_marker.

emit_simple_ready_marker is an almost verbatim copy of emit_ready_marker (Lines 50-75); only the written payload and the log message differ. The two loops will drift over time (e.g., a future change to MAX_ATTEMPTS/BACKOFF or the shutdown handling would need to be applied twice). A small private helper parameterized on the payload keeps both entry points in sync.

♻️ Proposed consolidation
async fn emit_marker(pubkey: Option<&PublicKey>) -> std::io::Result<()> {
    const MAX_ATTEMPTS: u32 = 50;
    const BACKOFF: Duration = Duration::from_millis(100);

    let addr = VsockAddr::new(VMADDR_CID_HOST, BOOT_MARKER_PORT);
    let mut last_err = None;
    for attempt in 1..=MAX_ATTEMPTS {
        match VsockStream::connect(addr).await {
            Ok(mut stream) => {
                match pubkey {
                    Some(pk) => write_ready_beacon(&mut stream, pk).await?,
                    None => stream.write_all(b"READY\n").await?,
                }
                AsyncWriteExt::shutdown(&mut stream).await?;
                tracing::info!(attempt, simple = pubkey.is_none(), "emitted boot READY marker");
                return Ok(());
            }
            Err(e) => {
                tracing::debug!(attempt, error = %e, "vsock not ready, retrying");
                last_err = Some(e);
                tokio::time::sleep(BACKOFF).await;
            }
        }
    }

    Err(last_err.unwrap_or_else(|| {
        std::io::Error::new(std::io::ErrorKind::TimedOut, "vsock never became available")
    }))
}

pub async fn emit_ready_marker(pubkey: &PublicKey) -> std::io::Result<()> {
    emit_marker(Some(pubkey)).await
}

pub async fn emit_simple_ready_marker() -> std::io::Result<()> {
    emit_marker(None).await
}
🤖 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 `@crates/minimald/src/guest.rs` around lines 82 - 107, The retry/connect logic
in emit_simple_ready_marker is duplicated from emit_ready_marker and should be
consolidated to avoid drift. Refactor both functions to call a shared private
helper (for example, a marker-emission helper in guest.rs) that owns the
VsockStream::connect loop, MAX_ATTEMPTS/BACKOFF handling, shutdown, and retry
logging, while letting emit_ready_marker and emit_simple_ready_marker differ
only by the payload/write step and final log message.
🤖 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 `@crates/minimald/src/guest.rs`:
- Around line 82-107: The retry/connect logic in emit_simple_ready_marker is
duplicated from emit_ready_marker and should be consolidated to avoid drift.
Refactor both functions to call a shared private helper (for example, a
marker-emission helper in guest.rs) that owns the VsockStream::connect loop,
MAX_ATTEMPTS/BACKOFF handling, shutdown, and retry logging, while letting
emit_ready_marker and emit_simple_ready_marker differ only by the payload/write
step and final log message.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 85c85271-5f45-4310-9c70-75ea67ba1a19

📥 Commits

Reviewing files that changed from the base of the PR and between bf05ff4 and be055bf.

📒 Files selected for processing (6)
  • crates/minimald/src/guest.rs
  • crates/minimald/src/main.rs
  • crates/minvmd/Cargo.toml
  • crates/minvmd/src/cmd/boot.rs
  • crates/minvmd/src/cmd/mod.rs
  • crates/minvmd/src/cmd/run.rs

@github-actions

Copy link
Copy Markdown

Revise claim for head be055bf.

@github-actions

Copy link
Copy Markdown

Auto-revise 1 of 3.

@gominimal-aw-bot gominimal-aw-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Generated by sdd-review for issue #582

Comment thread crates/minimald/src/guest.rs
Comment thread crates/minvmd/src/cmd/mod.rs
@gominimal-aw-bot

This comment has been minimized.

@gominimal-aw-bot

Copy link
Copy Markdown
Contributor Author

Commit pushed: 3fbe8b5

Generated by sdd-execute (sonnet tier)

@gominimal-aw-bot

This comment has been minimized.

@gominimal-aw-bot gominimal-aw-bot Bot mentioned this pull request Jun 26, 2026
@gominimal-aw-bot

Copy link
Copy Markdown
Contributor Author

Commit pushed: 21ffff9

Generated by sdd-execute (sonnet tier)

@gominimal-aw-bot

This comment has been minimized.

@gominimal-aw-bot gominimal-aw-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Generated by sdd-review for issue #582

Comment thread crates/minvmd/src/cmd/mod.rs
Comment thread crates/minimald/src/guest.rs Outdated
Comment thread crates/minvmd/src/cmd/mod.rs
@gominimal-aw-bot

Copy link
Copy Markdown
Contributor Author

Commit pushed: 81d6be5

Generated by sdd-execute (sonnet tier)

@gominimal-aw-bot

Copy link
Copy Markdown
Contributor Author

Commit pushed: 12a5afa

Addressed three review comments from the latest sdd-review pass:

  • r3480459287 (security)read_ready_beacon now caps the pubkey read_line at 4 096 bytes; a line longer than any valid OpenSSH key (Ed25519 ~80 B, RSA-4096 ~700 B) is logged as a warning and dropped without crashing the host process.
  • r3480459295 (coding standards)write_ready_beacon narrowed from pub to pub(crate); minimald is a binary crate with no external consumers and both call sites (same-file emit_marker and #[cfg(test)]) are covered by crate-level visibility.
  • r3480459300 (coding standards) — Removed the two ownerless TODO: pass instance_num through... comments; the hardcoded local-0 is intentional and documented in the spec Non-Goals, and there is no tracking issue for multi-instance support to link against.

All tests pass (minimald: 70, minvmd: 83; cargo fmt and clippy -D warnings clean).

Generated by sdd-execute (sonnet tier)

Generated by sdd-execute (sonnet tier) for issue #582 ·

@gominimal-aw-bot

This comment has been minimized.

@twitchyliquid64 twitchyliquid64 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Think this just needs the conflict caused by the crate rename minimal2->minimal fixed and then g2g

@norrietaylor

Copy link
Copy Markdown
Member

/revise resolve the conflict

@gominimal-aw-bot

Copy link
Copy Markdown
Contributor Author

Commit pushed: 9a200ec

Generated by sdd-execute (sonnet tier)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
crates/minimald/src/guest.rs (1)

551-555: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the beacon contains this key, not just any Ed25519 key.

This would still pass if write_ready_beacon emitted a different Ed25519 public key. Compare the second line with pubkey.to_openssh() to lock the READY contract.

Proposed test tightening
-        assert!(
-            key_line.starts_with("ssh-ed25519 "),
-            "expected openssh pubkey, got: {key_line:?}"
-        );
+        let expected = pubkey.to_openssh().unwrap();
+        assert_eq!(key_line, expected, "unexpected openssh pubkey line");
🤖 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 `@crates/minimald/src/guest.rs` around lines 551 - 555, The READY beacon test
in the code around the `key_line` assertion is too loose because it only checks
for an `ssh-ed25519` prefix. Tighten the check in the
`write_ready_beacon`/`pubkey.to_openssh()` test path by comparing the second
line directly against the expected OpenSSH string from `pubkey.to_openssh()`, so
the test verifies the exact beacon key rather than any Ed25519 key.
🤖 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 `@crates/minimald/src/main.rs`:
- Around line 429-444: The startup sequence in main should not emit the READY
marker before SSH is actually available. Update the init flow around
guest::emit_ready_marker, guest::bring_up_root_egress, and the vsock SSH
listener setup so READY is sent only after the listener is bound, or move
bring_up_root_egress earlier with a bounded timeout and keep SSH startup from
being delayed by slow gvproxy/tap setup. If egress must remain best-effort, run
it asynchronously after the listener is ready rather than blocking the READY
path.

In `@crates/minvmd/src/cmd/mod.rs`:
- Around line 26-32: The documentation for OWN_IP_ENV no longer matches the
actual gvproxy startup path in run_foreground. Update the constant comment on
OWN_IP_ENV to reflect that the parent supervisor now attempts to spawn gvproxy
whenever the binary is present, and that own_ip_requested() only controls
whether gvproxy failures are fatal or downgraded to warnings; keep the
child-side OwnIp vs HostNet behavior description aligned with the current logic.

---

Nitpick comments:
In `@crates/minimald/src/guest.rs`:
- Around line 551-555: The READY beacon test in the code around the `key_line`
assertion is too loose because it only checks for an `ssh-ed25519` prefix.
Tighten the check in the `write_ready_beacon`/`pubkey.to_openssh()` test path by
comparing the second line directly against the expected OpenSSH string from
`pubkey.to_openssh()`, so the test verifies the exact beacon key rather than any
Ed25519 key.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d4559b7f-a82c-4b61-9bbb-b5a12cdecdc3

📥 Commits

Reviewing files that changed from the base of the PR and between 81d6be5 and 9a200ec.

📒 Files selected for processing (7)
  • crates/minimald/Cargo.toml
  • crates/minimald/src/guest.rs
  • crates/minimald/src/main.rs
  • crates/minvmd/Cargo.toml
  • crates/minvmd/src/cmd/boot.rs
  • crates/minvmd/src/cmd/mod.rs
  • crates/minvmd/src/cmd/run.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/minvmd/Cargo.toml
  • crates/minvmd/src/cmd/boot.rs

Comment thread crates/minimald/src/main.rs
Comment thread crates/minvmd/src/cmd/mod.rs
gominimal-aw-bot Bot and others added 4 commits July 6, 2026 14:17
Extend the minvmd ready-beacon protocol from READY\n to
READY\n<openssh-pubkey>\n so the host can pre-populate known_hosts
before the first SSH connection, eliminating the TOFU prompt.

Guest side (minimald):
- Add write_ready_beacon(writer, pubkey) — the testable core logic
- Extend emit_ready_marker to accept &PublicKey and call it
- Add emit_simple_ready_marker for the degraded READY-only path
  (no SSH server, no key to advertise)
- Store host_private_key once in main.rs, reuse for both
  known_hosts write and the vsock beacon (R1.2)

Host side (minvmd):
- Add read_ready_beacon(reader, known_hosts_path) in cmd/mod.rs;
  reads the second line, parses it as a PublicKey, writes the
  known_hosts entry via learn_known_hosts_path (R2.1–R2.4)
- Add default_vm_known_hosts_path() helper
- Replace duplicated inline READY-reader in boot.rs and run.rs
  with the shared helper
- Promote russh from [dev-dependencies] to [dependencies] (R2.5)

Tests:
- minimald: write_ready_beacon_formats_two_lines (R1.1)
- minvmd: read_ready_beacon_writes_known_hosts_entry,
  read_ready_beacon_tolerates_missing_pubkey_line,
  read_ready_beacon_rejects_wrong_marker (R2.1–R2.4)

References #467
…dy_marker

Both public fns shared verbatim retry/connect logic. Extract a private
emit_marker(Option<&PublicKey>) helper; the two public fns become one-liners.
Use a structured `simple` field instead of separate log messages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a TODO comment at both sites that hardcode the hostname "local-0"
in `default_vm_known_hosts_path` and `read_ready_beacon`, noting that
instance_num should be threaded through once multi-instance support is
needed. This makes the intentional scope boundary explicit and prevents
a future silent mismatch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Narrow write_ready_beacon visibility from pub to pub(crate); minimald
  is a binary crate with no external consumers (coding-standards)
- Add 4096-byte length cap on the pubkey read_line in read_ready_beacon;
  bounds heap growth from a buggy/compromised guest before from_openssh
  can reject the input (security)
- Remove ownerless TODO comments for hardcoded local-0 hostname; single-
  instance is the declared scope per spec Non-Goals and no tracking issue
  exists for multi-instance support (coding-standards)
@norrietaylor
norrietaylor force-pushed the sdd/467-ssh-host-key-in-beacon-a3265025597e34bc branch from 9a200ec to 004cb8b Compare July 6, 2026 21:30
@gominimal-aw-bot

This comment has been minimized.

@gominimal-aw-bot gominimal-aw-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Generated by sdd-review for issue #582

Comment thread crates/minvmd/src/cmd/mod.rs
`read_ready_beacon_writes_known_hosts_entry` previously only asserted
that "local-0" appeared in the known_hosts output. A regression that
wrote the wrong key would still pass. The `openssh` variable (the exact
key sent in the beacon) was already in scope — add a second assertion
that the file also contains that key string, so the test verifies the
full R2.2 round-trip.
@gominimal-aw-bot

Copy link
Copy Markdown
Contributor Author

Commit pushed: c63a27d

Generated by sdd-execute (sonnet tier)

@gominimal-aw-bot

This comment has been minimized.

@gominimal-aw-bot gominimal-aw-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Generated by sdd-review for issue #582

Comment thread crates/minvmd/src/cmd/mod.rs Outdated
Read::take() is applied before read_line so the 4096-byte ceiling is
enforced at the I/O layer; the existing post-read length check is kept
as defence-in-depth. Also caps the READY-marker read at 32 bytes.
Adds a test for the oversized-pubkey case.
@gominimal-aw-bot

Copy link
Copy Markdown
Contributor Author

Commit pushed: 96f0a83

Generated by sdd-execute (sonnet tier)

@gominimal-aw-bot gominimal-aw-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Generated by sdd-review for issue #582

Comment thread crates/minvmd/src/cmd/mod.rs Outdated
Comment thread crates/minvmd/src/cmd/mod.rs Outdated
Comment thread crates/minvmd/src/cmd/mod.rs
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Revise claim for head 96f0a83.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Auto-revise 2 of 3.

@gominimal-aw-bot

Copy link
Copy Markdown
Contributor Author

sdd-validate findings — implementation boundary

Boundary resolved: Implementation (non-spec, non-architecture, non-spike diff)
Feature path: Fast-path — [sdd-fastpath: tracking=467 tier=sonnet]
Gate set applied: Implementation gates (proof artifacts, file scope, credentials)


Gate 1 — Proof artifacts re-executed and passing

Test proof — Unit 1: write_ready_beacon_formats_two_lines (R1.1)

Info — Deferred to consumer CI

Cannot execute locally: cargo test -p minimald fails with a CONNECT-tunnel 403 before exercising the change (crates.io registry unreachable in this environment). This is an infrastructure limit, not a code failure.

Covering check: the required ci-success status check (.github/workflows/ci.yml:106) depends on the test job, which runs cargo nextest run --workspace via .github/actions/core-tests. That job exercises crates/minimald including the write_ready_beacon_formats_two_lines test. Gate deferred to consumer CI; no hand-off.

Test proof — Unit 2: read_ready_beacon_writes_known_hosts_entry, read_ready_beacon_tolerates_missing_pubkey_line, read_ready_beacon_rejects_wrong_marker, read_ready_beacon_rejects_oversized_pubkey_line (R2.1–R2.4)

Info — Deferred to consumer CI

Same infrastructure limit as above (cargo test -p minvmd, CONNECT-tunnel 403). The required ci-successtestcargo nextest run --workspace path covers these tests. Gate deferred to consumer CI; no hand-off.

File proof — Unit 2: $XDG_STATE_HOME/minimal/providers/local-0/known_hosts after real boot

Blocker — Proof verified by no gate

Spec (docs/specs/06-spec-ssh-host-key-in-beacon/06-spec-ssh-host-key-in-beacon.md:99–101):

File: After minvmd run completes boot against a real guest, $XDG_STATE_HOME/minimal/providers/local-0/known_hosts exists and contains a valid entry for local-0 at port 22 matching the VM's SSH host key.

This proof cannot be executed in this environment (no VM runtime). Checking for a covering consumer required status check:

  • ci-success (the sole required check, .github/workflows/ci.yml:106): requires test, clippy, fmt, dogfood, cargo-deny, minimal-check. The test job runs unit/integration tests only — it does not boot a VM and does not inspect $XDG_STATE_HOME.
  • ci-linux-kvm.yml: boots a real VM and runs boot_e2e. However, this workflow is explicitly non-gating (ci-linux-kvm.yml:17–18: "NON-GATING: absent from ci.yml's ci-success, so a KVM/runner hiccup never blocks a PR"). It cannot serve as a required consumer gate.
  • boot_e2e.rs and minimald_session_e2e.rs: inspected — neither test contains a known_hosts assertion. The e2e tests do not check whether the file was written at the expected path.

The unit test read_ready_beacon_writes_known_hosts_entry (crates/minvmd/src/cmd/mod.rs:275) verifies that the code path writes the correct content to a temp path. It does not exercise default_vm_known_hosts_path() in the write call (it passes its own temp path), and it does not verify the runtime $XDG_STATE_HOME derivation end-to-end.

No required consumer status check covers this proof. A human must verify that after a real minvmd run boot, $XDG_STATE_HOME/minimal/providers/local-0/known_hosts is present and contains a local-0 entry matching the VM's SSH host key, before this PR merges.


Gate 2 — Changed files within task scope

Warning

The execution plan ([sdd-spec:fastpath-plan] comment on issue #467) lists these files in scope:

crates/minimald/src/guest.rs
crates/minimald/src/main.rs
crates/minvmd/src/cmd/boot.rs
crates/minvmd/src/cmd/run.rs
crates/minvmd/Cargo.toml

The PR also changes crates/minvmd/src/cmd/mod.rs (+148 lines: default_vm_known_hosts_path(), read_ready_beacon(), and four unit tests), which is not listed in the plan's scope.

Note: the spec itself anticipated this file (docs/specs/06-spec-ssh-host-key-in-beacon/06-spec-ssh-host-key-in-beacon.md:144–145: "factoring the read into a shared helper in cmd/mod.rs is advisable to keep the two in sync"), so the implementation follows spec guidance. The file is not a protected path. This is a Warning, not a Blocker — the execution-plan scope list was undersized relative to the spec's own suggestion.


Gate 3 — No real credentials in the diff

Clean

Scanned the diff. References to host_private_key and public_key are Rust identifier names, not embedded credentials. No secrets, tokens, PEM blocks, or API keys found.


Result

Gate Severity Summary
1 — Test proof (Unit 1) Info Deferred to ci-successtest job
1 — Test proofs (Unit 2) Info Deferred to ci-successtest job
1 — File proof (Unit 2) Blocker No required gate covers runtime known_hosts check
2 — Files in scope Warning cmd/mod.rs not in plan scope (anticipated by spec)
3 — Credentials Clean None found

One Blocker found. Applying needs-human. A human should boot a VM with this branch, confirm $XDG_STATE_HOME/minimal/providers/local-0/known_hosts is created and contains a local-0 entry matching the VM key, then clear needs-human to resume.

Fast-path note: no lifecycle move — sdd-execute is the declared writer of sdd:done on implementation PR merge (ADR 0012).

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • index.crates.io

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "index.crates.io"

See Network Configuration for more information.

Generated by sdd-validate for issue #582 ·

@gominimal-aw-bot gominimal-aw-bot Bot added the needs-human An agent handed off; a human must act, then clear this label. label Jul 6, 2026
- Check the pubkey length limit on the trimmed key so the trailing
  newline does not eat one character of the 4096 budget.
- Warn when both XDG_STATE_HOME and HOME are unset and the known_hosts
  path falls back to /tmp.
- Restore the TODO comments for the hardcoded local-0 hostname (added
  in 51e1df2, lost in a later revision) at both hardcoding sites.

Refs: #582

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Revise claim for head 4300513.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Auto-revise 3 of 3.

@norrietaylor
norrietaylor merged commit b78cd67 into main Jul 7, 2026
128 of 131 checks passed
@norrietaylor
norrietaylor deleted the sdd/467-ssh-host-key-in-beacon-a3265025597e34bc branch July 7, 2026 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human An agent handed off; a human must act, then clear this label.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants