8000 T126 — A host-address box's port that collides on the host is shown at session start and in `min ls` by minimal-foundry[bot] · Pull Request #2830 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

T126 — A host-address box's port that collides on the host is shown at session start and in min ls - #2830

Merged
norrietaylor merged 9 commits into
mainfrom
agent/2799-a-host-address-box-s-port-that-collides-on-the-h
Nov 2, 2026
Merged

norrietaylor merged 9 commits into
mainfrom
agent/2799-a-host-address-box-s-port-that-collides-on-the-h

Conversation

@minimal-foundry
Copy link
Copy Markdown
Contributor

T126 — A host-address box's port that collides on the host is shown at session start and in min ls

slice: S18
kind: task
covers: NET-138
size: M
est: 180 code, 240 tests
executor: agent
difficulty: routine
model: standard
diagnostics: min ls names each mirrored port that collided, so a person reads the collision in the listing and in the session-start output, not only in the minvmd log
observability: the existing minvmd warn line per collision stays; the control verb's reply count of collisions is a field on the ListSessions span
layers:
- host:
- crates/minvmd/src/box_registry.rs: NodeMirror::collisions() feeds a per-node list of collided ports with the time each was first seen, cleared when the port 8000 binds or the box ends
- crates/minvmd/src/control.rs: a read-only control verb returns the node's collided mirror ports to the host CLI; it carries ports only, no address, path or box name
test: cargo nextest run -p minvmd mirror_collisions_reported_over_control
- cli:
- crates/minimal/src/cmd/list.rs: when a VM reports collided mirror ports, min ls prints one row per VM: MIRROR PORTS: <vm> port <p> is held by another program on this machine, so a host_ip box cannot open it here (one port per sentence); --json carries mirror_collisions: [<port>, ...] per VM
- crates/minimal/src/resolver.rs: min session activate of a host_ip box on a VM host prints the same sentence for each collided port, after activation and outside an interactive attach
test: cargo nextest run -p minimal ls_shows_mirror_collisions
test: cargo nextest run -p minimal activate_names_collided_mirror_ports
produces: none
consumes: none
proof: With a host port already held, a host_ip box listening on that port shows the collision in min ls and in the activate output, naming the port.
depends on:
- blocked by #2699

Proof

When another program holds a host port and a host_ip box on a VM host listens on that port, min ls prints MIRROR PORTS: <vm> port <p> is held by another program on this machine, so a host_ip box cannot open it here. and min ls --json carries mirror_collisions: [<p>] for that VM. min session activate of a host_ip box without --attach prints the same sentence for each collided port after the box id. The tests prove this against a real minvmd control socket and a fake one. No end-to-end run against a live VM was made.

Notes from the agent: Out of layers: crates/minimal/src/cmd/boxes/create.rs has one new call to crate::resolver::print_mirror_collisions after println!("{id}"). The activate print site lives there, and resolver.rs alone cannot make activate print. The call runs only for a host_ip (HostNet) box on a minvmd host, and only when --attach is not given, so no line prints before an interactive shell.

Wire design: the new mirror_collisions verb is not a BoxControlRequest variant. A new variant needs crates/minimald-rpc, which is outside the layers. control.rs instead defines HostReadRequest and MirrorCollisionsReply, answers the line before the shared parse on the host door only, and exports minvmd::control::read_mirror_collisions for the CLI (minimal already depends on minvmd). A follow-up can move the verb into minimald-rpc. The guest door falls through to the usual parse refusal.

box_registry.rs: collided is now a BTreeMap<u16, Instant> with the time of the first collision. A new collisions_since() exposes it. The existing clearing paths stay: bind, stop notice, and every end of the binds (box end, range probe failure, node end).

Observability: cmd_ls records the total count on a ListSessions info span field mirror_collisions. The minvmd warn line per collision stays unchanged.

Printed text: the task fixes the sentence port <p> is held by another program on this machine, so a host_ip box cannot open it here. I copie…[truncated]

Tests

line exit tail
cargo nextest run -p minvmd mirror_collisions_reported_over_control 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-b821... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal ls_shows_mirror_collisions 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-c154... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal activate_names_collided_mirror_ports 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-d5af... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.

Verify

min task run agent-verify exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

kind": "GetNetSwitch",
                "parent_span_id": "49e3fddf35780890",
                "span_id": "28399596411d5474",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetDenyAllOptOut",
                "parent_span_id": "49e3fddf35780890",
                "span_id": "dadecc2dac3704ce",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "49e3fddf35780890",
                "span_id": "8958c9d3d25a1ab7",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "49e3fddf35780890",
                "queued_behind": "0",
                "span_id": "4b77bb3a02602e15",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "49e3fddf35780890",
                "queued_behind": "0",
                "span_id": "35af2ae03807051c",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "35af2ae03807051c",
                "span_id": "1addf1af086d610d",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   3.014s] ( 853/1515) minvmd box_registry::tests::a_closed_shuttle_detaches_rows_until_the_grace
        PASS [   2.217s] ( 854/1515) minvmd box_registry::tests::a_reattributed_box_keeps_its_row
        PASS [   5.009s] ( 855/1515) minvmd box_registry::tests::a_registration_refused_for_a_held_address_spends_no_switch_address
────────────
     Summary [  40.474s] 855/1515 tests run: 854 passed, 1 failed, 2 skipped
        FAIL [   2.816s] ( 852/1515) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 660/1515 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

Base

The base branch moved while the coder worked; it was merged in before the tests ran.

Out of scope

These files are outside the task's layers:, so this PR is a draft:

  • crates/minimal/src/cmd/boxes/create.rs

Closes #2799

The host control socket answers a read-only mirror_collisions verb
with the node's mirrored ports that collided with a holder on the
host. The node mirror keeps the time each port first collided.
min ls prints a MIRROR PORTS row per VM whose host_ip box ports are
held by another program on the host, and --json carries
mirror_collisions per VM. min session activate of a host_ip box on a
VM host prints the same sentence per port when it does not attach.
@coderabbitai
coderabbitai Bot commented Nov 1, 2026 •
Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f50399ae-e4f4-4aa1-937e-68fdfbbd1705

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@norrietaylor norrietaylor left a comment
Copy link
Copy Markdown
Member
< 8000 div hidden="hidden" data-view-component="true" class="js-comment-show-on-error flash flash-error flash-full">

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Babysitter review of 62dd7d2 against T126. The VM host daemon reports collided mirror ports over mirror_collisions. min ls adds one MIRROR PORTS row per VM that has any, --json carries mirror_collisions for each VM, and --raw pays no read. Activate prints the sentences on stderr only when it does not attach, so nothing lands before the shell paints (AGENTS.md healthy-start rule). The id on stdout stays the scripting contract. Each sentence names the port and says a host_ip box cannot open it, which is true for whichever box binds it. Nits, not blocking: the sentence starts lowercase ("port 8080 is held…"). The use super::*; in resolver.rs tests now sits after the new test. Activate runs right after create, before the box binds anything, so in practice it lists the VM's existing collisions.

@norrietaylor
norrietaylor marked this pull request as ready for review November 1, 2026 07:19
@norrietaylor
norrietaylor requested a review from a team as a code owner November 1, 2026 07:19
@minimal-foundry
minimal-foundry Bot commented Nov 1, 2026 •
Copy link
Copy Markdown
Contributor Author

Approved: no findings at 237f3de7d8d7.

Nothing to do.

Clobber: none seen.

@minimal-arch minimal-arch 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.

arch-review: PASS_WITH_CONDITIONS · 1 condition · the full ruling

This change reports the collided mirror ports from the host side, through a host-door verb that a guest cannot reach. That conforms to §7.1 lines 244 and 261. One gap is left. min ls changes a failed read of the VM host daemon into an empty list, so --json says mirror_collisions: [] when the CLI does not know. Line 254 does not allow a silent fallback. Make the change in C1: keep the read as an Option, and write null in --json when the CLI got no answer.

Ruled at 62dd7d2 against gominimal/arch@8640f85e193d by claude-opus-5-5.

Comment thread crates/minimal/src/cmd/list.rs Resolved
Comment thread crates/minimal/src/cmd/list.rs Resolved
@minimal-foundry minimal-foundry 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.

minimal-review — advisory review

Verdict COMMENT — 2 findings, 2 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/minimal/src/cmd/boxes/create.rs 8000 Resolved
Comment thread crates/minimal/src/cmd/list.rs Resolved
Read each VM's collided mirror ports concurrently, and keep a failed
read apart from an empty one: --json writes null for a VM with no
answer and [] only for an answered empty read.
@minimal-foundry minimal-foundry 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.

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

@minimal-arch minimal-arch 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.

arch-review: PASS_WITH_CONDITIONS · 1 condition · the full ruling

The change conforms to §7.1. The VM host daemon reports collided mirror ports on the host door only, as ports alone. min ls and min session activate show them. This head fixes --json: a VM with no answer is null, not []. The text table still skips that VM. A person then reads "no collisions" when the collisions are unknown, and line 261 requires the report in listings. Make the change in C1.

Ruled at 3860fbf against gominimal/arch@8640f85e193d by claude-opus-5-5.

Comment thread crates/minimal/src/cmd/list.rs Resolved
@norrietaylor
Copy link
Copy Markdown
Member

/revise Arch C1 and one review finding on 62dd7d2.

  1. Arch C1 (crates/minimal/src/cmd/list.rs cmd_ls, line 648): keep each VM's read as Option<Vec<u16>>, and drop the .unwrap_or_default() on minvmd::control::read_mirror_collisions.
    • Pass &[Option<Vec<u16>>] to format_ls_across_vms_with_mirrors_on and insert_mirror_collisions.
    • In --json, write mirror_collisions: null for a VM whose read returned None or that has no control socket. Write [] only for an answered empty read.
    • Fix the doc comment at line 696 to say so.
    • In ls_shows_mirror_collisions, assert that a None read renders null and prints no MIRROR PORTS row.
  2. Review LOW (list.rs:658): run the per-VM reads together with futures::future::join_all, so one VM that accepts but never answers does not add 2 s per VM to min ls.

Leave the activate hook as it is. The note about it on create.rs:2714 is tracked as a residual. Run this task's test lines.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

That /revise is held, not dropped. A coder is already running on #2799. Your note is kept and goes into the next round here, which starts when that coder finishes.

Nothing to type again: the command gets a rocket when its round starts, or a refusal that names the reason. No round was counted for it yet.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on the runtime's review findings, pushed as 3860fbf0eacc.

line exit tail
cargo nextest run -p minvmd mirror_collisions_reported_over_control 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-1ccb... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal ls_shows_mirror_collisions 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-bba0... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal activate_names_collided_mirror_ports 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-c779... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.

Verify min task run agent-verify exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

kind": "GetNetSwitch",
                "parent_span_id": "0332db72268d2fe5",
                "span_id": "da2049dde684a95e",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetDenyAllOptOut",
                "parent_span_id": "0332db72268d2fe5",
                "span_id": "53752fcf6c9187b0",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "0332db72268d2fe5",
                "span_id": "d4713a37e4c3b2f2",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "0332db72268d2fe5",
                "queued_behind": "0",
                "span_id": "784c4e922f8f6988",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "0332db72268d2fe5",
                "queued_behind": "0",
                "span_id": "a0741afe8de0321a",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "a0741afe8de0321a",
                "span_id": "4585f49f6c2071d6",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   3.017s] ( 859/1522) minvmd box_registry::tests::a_closed_shuttle_detaches_rows_until_the_grace
        PASS [   2.220s] ( 860/1522) minvmd box_registry::tests::a_reattributed_box_keeps_its_row
        PASS [   5.009s] ( 861/1522) minvmd box_registry::tests::a_registration_refused_for_a_held_address_spends_no_switch_address
────────────
     Summary [  41.705s] 861/1522 tests run: 860 passed, 1 failed, 2 skipped
        FAIL [   2.873s] ( 858/1522) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 661/1522 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

Outside the task's layers::

  • crates/minimal/src/cmd/boxes/create.rs

Converted to a draft: something above needs a person before review.

Review threads the coder judged:

  • 👍 valid — crates/minimal/src/cmd/list.rs: cmd_ls now keeps each VM's read as Option<Vec> and passes it through to the renderer. A failed read no longer becomes an empty list.
  • 👍 valid — crates/minimal/src/cmd/list.rs: insert_mirror_collisions writes null for a VM with no answer and [] only for an answered empty read. The doc comment says the same. ls_shows_mirror_collisions now pins the null case.
  • 👎 invalid — crates/minimal/src/cmd/boxes/create.rs:2714: The node-wide read is the task's contract: the control verb carries ports only, with no box name, so the CLI cannot filter by box. The timing is a known limit: the activate output shows the collisions the node has when the box activates, for example on a reactivation. A wait for the guest to listen
  • 👍 valid — PRRT_kwDOPdfcxM6yYKm5: cmd_ls reads all VMs together with futures::future::join_all, so a VM that does not answer costs one 2 s timeout in total. A failed read is now null in --json, not []. (not posted on the thread: not an open thread on the pull request)

With a host port already held, min ls prints a MIRROR PORTS: row that names the port for each VM that reports it. --json carries mirror_collisions per VM: the list for an answered read, and null for a VM that gave no answer. min session activate of a host_ip box on a VM host prints the same sentence for each collided port that the node reported when the box activated, when the command does not attach.

Notes from the agent: Round 1 changed only crates/minimal/src/cmd/list.rs. The test helper listing() was missing the running field that main added to VmListing, so the test target did not compile after the merge. This round adds running: true there. The activate hook stays in crates/minimal/src/cmd/boxes/create.rs. An earlier round put it there, outside the layers, because activate_box is where activation completes. resolver.rs holds the sentence and the reader. Limitation of the activate half of the proof: the node records a collision only when a guest reports a listening port. So a box activated for the first time usually shows its collision in min ls, and not in the activate output. The control verb carries ports only, by task design, so the activate output is node-wide.

@minimal-foundry
minimal-foundry Bot marked this pull request as draft November 1, 2026 07:44
min ls printed nothing for a running VM host that gave no answer, so
it looked the same as one with no collisions. It now prints one
MIRROR PORTS row that says the VM did not report its ports.
A native host has no VM host daemon. The fallback listing's control
socket there is minimald's, which does not serve the collided-port
read, so min ls printed a false MIRROR PORTS row on every native host.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 2 of 10 on a /revise note, pushed as 04224a034125.

line exit tail
cargo nextest run -p minvmd mirror_collisions_reported_over_control 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-b812... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal ls_shows_mirror_collisions 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-b061... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal activate_names_collided_mirror_ports 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-d4be... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.

Verify min task run agent-verify exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

   0.034s] ( 700/1522) minimal::bug a_malformed_config_is_reported_without_quoting_it
        PASS [   0.037s] ( 701/1522) minimal::bug an_invalid_link_table_is_reported_without_quoting_it
        PASS [   0.048s] ( 702/1522) minimal::bug bug_no_guest_makes_no_daemon_contact
        PASS [   0.109s] ( 703/1522) minimal::bug bug_with_daemon_nests_a_verified_guest_bundle
        PASS [   0.043s] ( 704/1522) minimal::bug bug_with_stale_socket_reports_the_connect_stage_and_falls_back
        PASS [   0.040s] ( 705/1522) minimal::bug bug_without_daemon_still_produces_a_bundle
        PASS [   0.035s] ( 706/1522) minimal::bug incident_collectors_land_and_mask_macs
        PASS [   0.048s] ( 707/1522) minimal::bug logs_collects_newest_five_per_prefix_and_host_logs
        PASS [   0.043s] ( 708/1522) minimal::bug planted_secrets_never_reach_the_bundle
        PASS [   0.035s] ( 709/1522) minimal::bug the_bundle_names_the_project_it_came_from
        PASS [   0.037s] ( 710/1522) minimal::bug the_invoking_terminal_is_described
        PASS [   4.302s] ( 711/1522) minimal tests::ls_leashes_the_vms_it_did_not_select
        PASS [   0.040s] ( 712/1522) minimal::bug the_recorded_project_root_is_resolved_not_echoed
        FAIL [   0.154s] ( 713/1522) minimal::cli activate_and_ls_report_native_surface
  stdout ───

    running 1 test
    test activate_and_ls_report_native_surface ... FAILED

    failures:

    failures:
        activate_and_ls_report_native_surface

    test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 75 filtered out; finished in 0.14s

  stderr ───

    thread 'activate_and_ls_report_native_surface' (5017) panicked at crates/minimal/tests/cli.rs:3793:5:
    a host with no hook must not be told native DNS is live: RESOURCE POOL:  4 CPU cores · 11.9 GiB memory · shared by 0 sessions

    HOSTNAME PROXY:  listening on 127.0.0.1:37013 as the HTTP proxy for <box>.min.internal names
    DNS SERVICE:     listening on 127.0.0.1:57558 (UDP) for *.min.internal
    BOX NAMES:       <box>.min.internal opens through the HTTP proxy at 127.0.0.1:37013; finish setup: min finalize-install

    No active sessions.
    MIRROR PORTS:  default  the VM did not report which ports are held by another program on this machine.

    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   0.297s] ( 714/1522) minimal::cli activate_creates_session
        PASS [   2.040s] ( 715/1522) minimal tests::stop_without_force_on_a_wedged_vm_names_the_recovery
        PASS [   7.047s] ( 716/1522) minimal tests::stop_force_stops_a_wedged_vm_from_the_host
────────────
     Summary [  19.683s] 716/1522 tests run: 715 passed, 1 failed, 2 skipped
        FAIL [   0.154s] ( 713/1522) minimal::cli activate_and_ls_report_native_surface
warning: 806/1522 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

Outside the task's layers::

  • crates/minimal/src/cmd/boxes/create.rs

Converted to a draft: something above needs a person before review.

Review threads the coder judged:

  • 👎 invalid — crates/minimal/src/cmd/boxes/create.rs:2715: The /revise note for this round says to leave the activate hook as it is, and tracks this point as a residual. The read is node-wide and runs at activate, as the earlier reply on this point says. No change this round.
  • 👍 valid — crates/minimal/src/cmd/list.rs: A running VM host listing (running, with a control socket) whose read gave no answer now prints MIRROR PORTS: <vm> the VM did not report which ports are held by another program on this machine. A stopped VM prints no row. ls_shows_mirror_collisions pins both cases.

With a host port already held, a host_ip box listening on that port shows the collision in min ls (a MIRROR PORTS row that names the port, and mirror_collisions in --json) and in the activate output. A running VM host that does not answer the read now gets its own MIRROR PORTS row, and --json writes null for it, so it never looks like a VM with no collisions.

Notes from the agent: Note items 1 and 2 (Option<Vec> reads, null in --json, the doc comment, the None assertion, join_all) were already on the branch in 3860fbf, so this round adds no change for them. This round adds the arch C1 row for a running VM host with a control socket whose read gave no answer: MIRROR PORTS: <vm> the VM did not report which ports are held by another program on this machine. A stopped VM, or a listing with no VM host daemon (control_sock None), prints no row. That keeps the note's assertion that a None read prints no row for the test listing, which has no control socket. The activate hook is unchanged, as the note asks. cargo clippy -p minimal --all-targets reported nothing in list.rs. I did not run just clippy-strict.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 3 of 10 on a failed check suite, pushed as 0f1797740a71.

line exit tail
cargo nextest run -p minvmd mirror_collisions_reported_over_control 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-7c22... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal ls_shows_mirror_collisions 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-bcf7... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal activate_names_collided_mirror_ports 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-8c12... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.

Verify min task run agent-verify exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

id": "a5f773938dbce706",
                "span_id": "1098653a02fed852",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetDenyAllOptOut",
                "parent_span_id": "a5f773938dbce706",
                "span_id": "efce0dceb2c22d58",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "a5f773938dbce706",
                "span_id": "412be7dd6cce7f14",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "a5f773938dbce706",
                "queued_behind": "0",
                "span_id": "9c58a3eb9eeed634",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "a5f773938dbce706",
                "queued_behind": "0",
                "span_id": "e30b586545986109",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "e30b586545986109",
                "span_id": "faa7165f3bc43893",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   2.221s] ( 863/1522) minvmd box_registry::tests::a_relay_already_carrying_a_source_re_attributes_a_reinstated_row
        PASS [   5.010s] ( 864/1522) minvmd box_registry::tests::a_registration_refused_for_a_held_address_spends_no_switch_address
        PASS [   3.212s] ( 865/1522) minvmd box_registry::tests::a_reinstated_unit_a_task_run_carries_is_not_withdrawn_at_the_bound
────────────
     Summary [  45.175s] 865/1522 tests run: 864 passed, 1 failed, 2 skipped
        FAIL [   4.672s] ( 862/1522) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 657/1522 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

Outside the task's layers::

  • crates/minimal/src/cmd/boxes/create.rs

Converted to a draft: something above needs a person before review.

With a host port already held, a host_ip box listening on that port shows the collision in min ls (a MIRROR PORTS row naming the port, and mirror_collisions in --json) and in the min session activate output. On a native host with no VM, min ls now prints no MIRROR PORTS row.

Notes from the agent: The failed tests check was minimal::cli activate_and_ls_report_native_surface. On a native Linux host, the fallback listing carries minimald's control socket. minimald does not serve the collided-port read, so the read returned None and min ls printed the 'the VM did not report' row on every native host. Fix: cmd_ls reads collided mirror ports only when global.use_minvmd() is true. On a native host, --json carries mirror_collisions: null. ci-linux-native-success is the lane aggregator and failed only because tests failed. crates/minimal/src/cmd/boxes/create.rs is outside the layers. An earlier round changed it to call the activate print, and this round did not touch it.

@norrietaylor norrietaylor 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.

Babysitter review of 0f17977. Arch C1 is met. An unread VM is null in --json and gets a MIRROR PORTS row saying the VM did not report its ports. An answered empty read is []. The reads run together, and a native host makes none. The activate-hook note is accepted as a residual: min ls is the lasting surface.

@norrietaylor
norrietaylor marked this pull request as ready for review November 1, 2026 19:27
@minimal-arch minimal-arch 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.

arch-review: CONDITIONS · 1 condition · the full ruling

min ls now names a running VM host that does not report its collided ports. This conforms to §7.1 lines 254 and 261. min session activate still uses unwrap_or_default() on the same read, so an unread VM host prints nothing at session start. Section 7.1 line 261 requires the report at session start, and line 254 forbids a silent fallback. Make the change in C1. Then print the unread line on activate and update the test that pins the empty result.

Ruled at 0f17977 against gominimal/arch@8640f85e193d by claude-opus-5-5.

Comment thread crates/minimal/src/resolver.rs Resolved
Comment thread crates/minimal/src/resolver.rs Resolved
@minimal-foundry minimal-foundry 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.

minimal-review — advisory review

Verdict COMMENT — 3 findings, 3 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/minimal/src/cmd/boxes/create.rs Resolved
Comment thread crates/minimal/src/cmd/list.rs Resolved
Comment thread crates/minimal/src/cmd/list.rs Resolved
min session activate now prints the unread sentence that min ls prints
when the VM host daemon gives no answer. The ListSessions span wraps the
collided-port reads, and a native host's --json carries no
mirror_collisions field.
@minimal-arch minimal-arch 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.

arch-review: PASS · the full ruling

This head conforms to §7.1. A VM host that did not answer the collided-port read is now named at activate, as min ls already names it. So no read failure looks like a VM with no collisions (line 254, line 261). The verb stays on the host door, after the peer uid check, and its reply carries ports only (line 244). One doc comment is out of date: read_mirror_collisions says the caller prints nothing on a failed read. Correct it when you next touch the file. It is not a condition.

Ruled at bb43339 against gominimal/arch@8640f85e193d by claude-opus-5-5.

@minimal-foundry minimal-foundry 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.

minimal-review — approval withheld

Verdict COMMENT — APPROVE withheld: 5 unresolved threads — minimal-arch[bot] crates/minimal/src/resolver.rs, minimal-arch[bot] crates/minimal/src/resolver.rs:4988, minimal-foundry[bot] crates/minimal/src/cmd/boxes/create.rs:2714, minimal-foundry[bot] crates/minimal/src/cmd/list.rs:694, minimal-foundry[bot] crates/minimal/src/cmd/list.rs:691. No findings.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

@norrietaylor
Copy link
Copy Markdown
Member

/revise Arch C1 and two review findings on 0f17977.

  1. Arch C1 (crates/minimal/src/resolver.rs print_mirror_collisions, around line 4988): replace .unwrap_or_default() on minvmd::control::read_mirror_collisions(&sock).
    • When the read returns None, or control_sock is None on a VM host, print one stderr line with the same wording as min ls's unread row: the VM did not report which ports are held by another program on this machine. Share one helper with crate::cmd::list::mirror_collision_unread_row, and return the line.
    • In activate_names_collided_mirror_ports, the absent.sock and None cases expect that line, not an empty Vec.
    • The hook still runs only when the activation does not attach, so nothing prints before a shell.
  2. Review LOW (crates/minimal/src/cmd/list.rs:694): the ListSessions span is created and recorded but never entered. Instrument the reads with it (.instrument(span)), or enter it, so the mirror_collisions count reaches the trace.
  3. Review LOW (list.rs:691, insert_mirror_collisions): when no read happened (--raw, or a native host that is not using the VM host daemon), do not add mirror_collisions to the JSON at all. null means "the VM gave no answer", and a native host never asks. Pin it: a native listing's --json has no mirror_collisions key.

Run this task's test lines.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

That /revise is held, not dropped. A coder is already running on #2799. Your note is kept and goes into the next round here, which starts when that coder finishes.

Nothing to type again: the command gets a rocket when its round starts, or a refusal that names the reason. No round was counted for it yet.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 4 of 10 on the runtime's review findings, pushed as bb433394f5c0.

line exit tail
cargo nextest run -p minvmd mirror_collisions_reported_over_control 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-dea7... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal ls_shows_mirror_collisions 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-5dea... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal activate_names_collided_mirror_ports 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-312b... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.

Verify min task run agent-verify timed out after 1800s.

md::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>300.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>360.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>420.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>480.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>540.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>600.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>660.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>720.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>780.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>840.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>900.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>960.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>1020.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>1080.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>1140.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>1200.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>1260.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>1320.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>1380.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>1440.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>1500.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline
        SLOW [>1560.000s] (─────────) minimal cmd::auth::tests::a_stalled_install_read_on_the_callback_does_not_outlive_the_deadline

Outside the task's layers::

  • crates/minimal/src/cmd/boxes/create.rs

Converted to a draft: something above needs a person before review.

Review threads the coder judged:

  • 👍 valid — crates/minimal/src/resolver.rs: A failed read in print_mirror_collisions now prints mirror_collision_unread_sentence, the same text as the min ls unread row.
  • 👍 valid — crates/minimal/src/resolver.rs:4988: A missing control socket on a VM host now prints the unread sentence too. The test pins both cases.
  • 👍 valid — crates/minimal/src/cmd/boxes/create.rs:2714: Fixed the unread part: an unanswered read now prints the unread sentence, as min ls does. The node-wide read is by design, because the task's control verb carries ports only and no box name, and the sentence says 'a host_ip box', not 'this box'. The single read after activation is what the task spec
  • 👍 valid — crates/minimal/src/cmd/list.rs:694: The collided-port reads now run under .instrument(span.clone()), so the ListSessions span is entered and carries the recorded count.
  • 👍 valid — crates/minimal/src/cmd/list.rs:691: insert_mirror_collisions now adds no field when no read happened (empty slice: native host or --raw). A test pins that native --json has no mirror_collisions.

With a host port already held, a host_ip box that listens on that port shows the collision in min ls and in the min session activate output, and each names the port. When the VM host does not answer the read, both surfaces say that the VM did not report, and they do not show an empty list.

Notes from the agent: Revise round 4. The unread sentence is now one function, mirror_collision_unread_sentence, in resolver.rs. min ls and activate print it with the same words. A native host's --json no longer carries mirror_collisions. The ListSessions span now wraps the collided-port reads through .instrument. The activate read still runs once, directly after activation, and it reads the whole node. The task specifies both: the control verb carries ports only, and the sentence says 'a host_ip box', not 'this box'.

@minimal-foundry
minimal-foundry Bot marked this pull request as draft November 1, 2026 20:15
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 5 of 10 on a /revise note: the coder changed nothing. No change this round. The branch head bb43339 already addresses the three items in the note, which cites 0f17977. (1) print_mirror_collisions prints mirror_collision_unread_sentence() for a failed read and for a None socket. mirror_collision_unread_row in list.rs uses the same helper, and the test expects that line for absent.sock and None. The call in create.rs still runs only when the activation does not attach. (2) The collided-port reads in cmd_ls run under .instrument(span) on the ListSessions span. (3) insert_mirror_collisions adds no field when no read happened, and ls_shows_mirror_collisions checks that a native --json listing has no mirror_collisions key. All three test lines pass at the head.

@minimal-foundry minimal-foundry 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.

minimal-review — approved

Verdict APPROVE — every review thread is resolved; @norrietaylor closed the last one. The review of bb433394f5c0 withheld this approval on the threads open then. Posted as a comment: the App cannot approve its own pull request; the check and the label carry it.

@minimal-foundry minimal-foundry Bot added the review:approved minimal-review approved the current head label Nov 1, 2026
@norrietaylor norrietaylor 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.

Babysitter review of bb43339. Arch C1 is met: activate prints the unread sentence for a VM host that gave no answer, the same as min ls. The span is instrumented, and a native or --raw listing adds no mirror_collisions key. The arch review is PASS at this head. The activate-timing note stays as an accepted residual.

@norrietaylor
norrietaylor marked this pull request as ready for review November 1, 2026 21:04
…ress-box-s-port-that-collides-on-the-h

# Conflicts:
#	crates/minimal/src/cmd/list.rs
@minimal-arch minimal-arch 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.

arch-review: PASS · the full ruling

The change still conforms to §7.1. This head only merges main. The list.rs conflict resolution passes the new proxy-port reads through to the listing and keeps the MIRROR PORTS rows and the mirror_collisions JSON field as they were. The host-door read in control.rs is unchanged, and the new proxy_ports verb does not match it. The PASS on bb433394 carries to this head.

Ruled at 237f3de against gominimal/arch@8640f85e193d by claude-opus-5-5.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

A merge of main before round 6, not counted against the cap (1 of 3), pushed as 237f3de7d8d7.

Resolved conflicts in: crates/minimal/src/cmd/list.rs

line exit tail
cargo nextest run -p minvmd mirror_collisions_reported_over_control 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-c82b... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal ls_shows_mirror_collisions 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-3374... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal activate_names_collided_mirror_ports 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-8fae... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.

Verify min task run agent-verify exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

kind": "GetNetSwitch",
                "parent_span_id": "4eb545bc97ab886e",
                "span_id": "2e28fafe91ec1efc",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetDenyAllOptOut",
                "parent_span_id": "4eb545bc97ab886e",
                "span_id": "be6c30df974b2bfd",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "4eb545bc97ab886e",
                "span_id": "09b86e8fbb74d653",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "4eb545bc97ab886e",
                "queued_behind": "0",
                "span_id": "7857f0b29d684e1a",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "4eb545bc97ab886e",
                "queued_behind": "0",
                "span_id": "e76c365e35e7d8ac",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "e76c365e35e7d8ac",
                "span_id": "00210a401c79a118",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   3.019s] ( 860/1528) minvmd box_registry::tests::a_closed_shuttle_detaches_rows_until_the_grace
        PASS [   2.220s] ( 861/1528) minvmd box_registry::tests::a_reattributed_box_keeps_its_row
        PASS [   5.010s] ( 862/1528) minvmd box_registry::tests::a_registration_refused_for_a_held_address_spends_no_switch_address
────────────
     Summary [  43.412s] 862/1528 tests run: 861 passed, 1 failed, 2 skipped
        FAIL [   2.936s] ( 859/1528) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 666/1528 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

Outside the task's layers::

  • crates/minimal/src/cmd/boxes/create.rs

Converted to a draft: something above needs a person before review.

With a host port already held, a host_ip box that listens on that port shows the collision in min ls as a MIRROR PORTS row that names the port, and in the min session activate output. After the merge with main, min ls also keeps the hostname proxy ports that main added.

Notes from the agent: This round resolved the merge conflict with main in crates/minimal/src/cmd/list.rs. cmd_ls now reads both the collided mirror ports (T126) and the hostname proxy ports (NET-154). format_ls_across_vms_with_mirrors_on takes a new vm_proxy_ports slice and renders through format_ls_across_vms_with_proxy_ports_on. Thus the --json output and the table carry both. The function now has 8 arguments, so it carries an #[expect(clippy::too_many_arguments)] with a reason. clippy -p minimal --all-targets is clean. The other files in the merge commit come from main and are not changes from this task.

@minimal-foundry
minimal-foundry Bot marked this pull request as draft November 2, 2026 04:51
@norrietaylor norrietaylor 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.

Babysitter re-approval at 237f3de: this head only merges origin/main onto bb43339, which I approved. Arch PASS, and all lanes are green.

@norrietaylor
norrietaylor marked this pull request as ready for review November 2, 2026 17:12
@norrietaylor
norrietaylor added this pull request to the merge queue Nov 2, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 2, 2026
@norrietaylor
norrietaylor added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit a97c477 Nov 2, 2026
36 checks passed
@norrietaylor
norrietaylor deleted the agent/2799-a-host-address-box-s-port-that-collides-on-the-h branch November 2, 2026 20:18
norrietaylor added a commit that referenced this pull request Nov 3, 2026
* fix(deps): bump pulldown-cmark to 0.13.6 for RUSTSEC-2026-0433 (#2846)

Claude-Session: https://claude.ai/code/session_01NSAwDroXL6KqsSxUzdbZGw

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(minimald): hand a reaped name over in one manager step (#2842)

A reap read the records and then sent the handover as a second message.
A removal that began between the two left the name with no row while
another retained box still had it. The manager now releases the reaped
box's row, picks the successor among boxes not being removed, and
registers it in one message. The reap and the prune return after both.

The daemon log names the release (name-released) before the handover
(name-handed-over), both with box ids.

Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>

* T126 — A host-address box's port that collides on the host is shown at session start and in `min ls` (#2830)

* feat(minvmd): answer a node's collided mirror ports over control

The host control socket answers a read-only mirror_collisions verb
with the node's mirrored ports that collided with a holder on the
host. The node mirror keeps the time each port first collided.

* feat(minimal): show collided mirror ports in min ls and activate

min ls prints a MIRROR PORTS row per VM whose host_ip box ports are
held by another program on the host, and --json carries
mirror_collisions per VM. min session activate of a host_ip box on a
VM host prints the same sentence per port when it does not attach.

* fix(minimal): show an unread VM's mirror ports as null in min ls

Read each VM's collided mirror ports concurrently, and keep a failed
read apart from an empty one: --json writes null for a VM with no
answer and [] only for an answered empty read.

* fix(minimal): name a VM host whose mirror ports were not read

min ls printed nothing for a running VM host that gave no answer, so
it looked the same as one with no collisions. It now prints one
MIRROR PORTS row that says the VM did not report its ports.

* fix(minimal): skip the mirror-port read on a native host

A native host has no VM host daemon. The fallback listing's control
socket there is minimald's, which does not serve the collided-port
read, so min ls printed a false MIRROR PORTS row on every native host.

* fix(minimal): name an unread VM host at activate, enter the span

min session activate now prints the unread sentence that min ls prints
when the VM host daemon gives no answer. The ListSessions span wraps the
collided-port reads, and a native host's --json carries no
mirror_collisions field.

---------

Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>

* T42 — Start every box's entrypoint from its spec through one launcher (#2841)

* feat(mfile): carry entrypoint and lifetime in the box spec

The spec gains an [execution] table with the box's lifetime and, for a
box that does not run its login shell, its entrypoint argv. A v0 spec
stored before the table existed reads with the entrypoint its type
implies.

* feat(minimald): start each box's entrypoint from its spec

One launcher decision, LaunchSpec::entrypoint, reads the first process
from the box's stored spec: the login shell, or the spec's argv. The
actor reads the stored spec once, keeps its hold, and hands it to every
launcher. A launch minted for an exec keeps the shell, because the exec
runs the box's command. Each launch logs its entrypoint in a
box.launch span.

* fix(minimald): keep the shell on every launch of an exec-run box

A task's run arrives over the exec channel. A hook launch or an attach
can come before or after that exec, and a hook launch's gated first
process starts at the next attach. Only the exec-minted launch held
the shell, so a task box could run its task twice, and an attach got
the task, not a shell. Every launch now says an exec carries the run,
and the launcher starts the shell for it.

* refactor(mfile): write box.lifetime and execution.exec in the spec

Rename Lifetime::Persistent to UntilStopped, move the lifetime to
[box] with a until_complete default for v0 specs, and serialize the
entrypoint as execution.exec. List the argv shape as a v0 exception.

* fix(minimald): refuse a launch whose stored spec cannot be read

held_spec returns an error for unreadable spec bytes or a failed store
read, and launch_host refuses with SpawnFailed instead of starting the
shell. Mark run_by_exec as an interim that T43 (#2405) removes.

* test(minimald): store a readable spec in the stopped-box record test

A launch now refuses a stored spec it cannot read, so the test's
placeholder bytes failed its ensure_host. Store a session spec instead.

* docs(mfile): say that no launch reads the spec's lifetime yet

A box ends when its entrypoint returns, whatever box.lifetime says.

* fix(minimald): keep entrypoint arguments out of the launch log

The box.launch span and its info event printed every argument of an
argv entrypoint. An argument can carry a secret, so they now name the
program (or the login shell) and the argument count only. A test pins
that a secret-looking argument does not reach the log.

---------

Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
Co-authored-by: Karl Godard <karl@minimal.dev>

* T30 — Run a command in a box with a terminal or detached, under an exec id (#2843)

* feat(minimald-rpc): carry exec mode, timeout and exec id on the wire

min://exec carries an argv with its mode (attached, tty or detached) and
an optional timeout. min://exec/attach, min://exec/output and
min://exec/wait name an exec by its UUIDv7 id. The box store places
each exec's transcript beside the record and appends events by path.

* feat(minimald): run box execs under an id, with a terminal or detached

Every command for a box gets a UUIDv7 exec id, and a box that is not
running refuses it with exit 2 naming its state. min://exec runs an
argv with no shell: attached, on a daemon terminal, or detached with
stdin closed. Tty and detached execs answer with their id at once,
capture their output beside the box record, and keep their exit for
min://exec/wait. An exec's own timeout stops it and records an
exec_exited event with reason timeout, and the box keeps running.

* style(minimald-rpc): format the exec id parse and its tests

* fix(minimald): bound the wait after an exec timeout; drop reaped execs

A command that survives the stop at its timeout no longer holds its
wait open: the wait after the stop is bounded, then SIGKILLed and
recorded as a timeout. Ended execs of a reaped box leave the exec map.

* fix(minimald): exit 124 on exec timeout; wait reads stored exit

An exec that its own timeout ends now exits 124, attached or detached.
A wait on an exec that this daemon holds no entry for reads the box's
stored exec_exited event, so a detached exec's exit code outlives a
daemon restart.

* fix(minimald): cap exec transcripts at 8 MiB and stream reads

A detached exec now writes through a pipe and a pump, as a tty exec
does, so the daemon can cap its transcript. Past the cap the transcript
keeps a mark and the newest half of the cap, and the command keeps
running. The output read and the attach replay copy the transcript in
64 KiB chunks, up to its length at the open, and the attach takes the
terminal lock only to open the file and subscribe.

* fix(minimald): bound tty exec input; no deadline past the clock

A tty exec's terminal input went through an unbounded channel, so a
program that never reads stdin let the daemon queue grow without
limit. The terminal now holds typed input in a HeldInput with the
attached path's bounds (EXEC_INPUT_HELD_MAX per exec,
EXEC_INPUT_HELD_DAEMON_MAX for the daemon). Past either bound the
chunk is dropped and the log says so once per exec. The exec's end
releases what it held.

An attached exec's timeout too large for the clock panicked after the
spawn. timeout_deadline now uses checked_add and gives no deadline.

Tests: unread tty input stays within the cap, a timeout of u64::MAX
seconds runs the exec to its end, and an exec id from one box finds no
exec, output or exit in another.

* fix(minimald): wait on exec terminals in the runtime, not a thread

The tty feed wrote to the terminal from a blocking thread. When the
program never read its input, that write never returned, and each such
exec left a blocking thread behind for good. The terminal and the
detached pipe are now non-blocking and watched through AsyncFd, so the
supervisor can stop the pump and the feed at the exec's end. Both stop
on a closed state too, because tokio never clears it and a wait for it
returns at once.

---------

Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
Co-authored-by: Karl Godard <karl@minimal.dev>

* fix(minimald): hand a destroyed box's name to the next holder (#2849)

A destroy removed the box that held a name's row and left the name
with no row, while another retained box still had that name. The
destroy now runs the same handover as a reap: the row goes to the
running box with the name, else to the most recent creation. The
destroy answers after the handover.

The daemon log names the destroyed box and the successor, and the
destroy span records the successor id or "none".

Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>

* T114 — `min ls` and the box commands tell the truth, end to end (#2839)

* test(e2e): prove min ls and the box commands across two VMs

Adds the ls_and_box_commands_tell_the_truth case: a stopped VM's boxes
in min ls, the ADDRESS and EGRESS cells against min session policy,
policy and forward without --vm, the unknown-box and stopped-VM
refusals, both proxy ports after the preferred port frees, and the
--public and out-of-range expose refusals.

* test(e2e): record ls_and_box gates as NOT RUN, check declared rules

The four gates of ls_and_box_commands_tell_the_truth now call not_run,
so the lane summary names the case. A 'declared rules' EGRESS cell is
now checked against the subnets rule row that min session policy
prints, not only against an egress heading.

* test(e2e): read the expose address past a trailing carriage return

The ls_and_box case read the box's address from the in-box expose reply
with an end-of-line anchor. A reply over the exec channel can end in a
carriage return, which left the address empty.

* test(e2e): brace lt_port before a bracket class in the expose read

shellcheck reads $lt_port[[:space:]] as an array index (SC1087).
The braces keep the same match and make the script lint-clean again.

* fix(minimal): poll the owning VM in min net forward

A forward to a box on another VM polled the selected VM for its
session. That VM does not hold the box, so the forward closed on the
first tick. Reconnect the poll to the owning VM, version-gated.

---------

Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
Co-authored-by: Norrie Taylor <91171431+norrietaylor@users.noreply.github.com>

* docs(spec): specify dns steering and the host_ip lane for spec 24 (#2851)

* docs(spec): specify dns steering and the host_ip lane for spec 24

Add BEP-080 to BEP-087 so the default `dns` steering works for own_ip
and host_ip boxes on a VM host, as architecture D6 and networking §5.4
require.

- BEP-080, BEP-081: direct TLS to the proxy on port 443, routed by
  SNI, and admitting dns and both on a VM host (#2805).
- BEP-082 to BEP-087: the host_ip lane: a host-side box id, the cohort
  proxy lane, cohort-attributed delivery with the named box's egress
  and audit subject, the in-effect rule, per-box steering, and the
  live proof.
- BEP-015 gains a host_ip proxy_env case.
- Security considerations states the cohort lane and per-box steering.

* docs(spec): keep the first slice paragraph unchanged and move the host_ip e2e checks to BEP-087

* docs(spec): address review on the host_ip lane requirements

- BEP-083: the cohort lane also admits TCP 443 at the proxy's address,
  the direct TLS path dns steering uses.
- BEP-084: refuse and audit a cohort-attributed value that names no
  live host_ip box of this host.
- BEP-086: cite NET-003's VM-backed resolver requirement, not NET-079.
- BEP-087: the proxy_env case is its own sub-requirement.
- Security considerations: BEP-083 and BEP-084 join the redemption
  invariant, and BEP-084 the egress invariant.

* T10 — Log a box's output from its start, out of the box's reach (#2852)

* feat(boxes): name a box's output log beside its record

Adds LOG_FILE and BoxDir, the box's directory as code outside the store
sees it: the log path, a lookup by id through the index, a log read by
offset, and an event append for a writer that holds no DiskBox.

* feat(minimald-rpc): add a streamed read of a box's output log

* feat(minimald): log a box's output from its start

The host appends every read from the box's terminal to its output log
before it hands the bytes to an attached client, so the client is a
second reader. A binding whose client closes its channel, drops its
connection or is shed records a detached event, and the box runs on.

* feat(minimald): serve a box's output log by its id

ReadLog streams the log from a byte offset, and with follow sends new
output until the box is reaped. A test shows that files written into
the box's own directories reach neither the log nor the record.

* fix(minimald): read a box's log on the blocking pool

The followed log read polled the store with blocking std::fs calls on an
async task. The find, the chunk reads and the retained check now run
through spawn_blocking, so a slow disk does not hold a runtime worker.

* fix(boxes): cap a box's output log and read it across a drop

The log keeps a drop mark and the newest half past 8 MiB. A reader
keeps its file open and moves to the replaced file with no byte sent
twice. The read refuses a link or a file that is not regular.

* fix(minimald): start no store box without its output log

BoxOutputLog::open returns the open's error for a box in the store, and
Host::build stops before the launch. The log writes through the capped
store writer, and ReadLog keeps one reader for the whole stream.

* fix(minimald): run detach hooks when a PTY client is lost

A lost client already records a detached event. Its on_detach hooks now
run too, so the event and the hooks agree. A test drives a box past the
log cap.

* style: format the log cap tests

* docs(boxes): mark the 8 MiB log cap as not yet in the design

The cap drops the start of a box's output past 8 MiB. That departs from
architecture.md line 473. gominimal/arch#159 decides the bound.

* docs(specs): record the interim 8 MiB box log cap in BOX

The decision that logs a box's output from its start now states the
8 MiB cap, what the log keeps past it, and the arch#159 decision that
removes the cap or changes BOX-155.

---------

Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>

* T9 — End a box when its entrypoint returns, never for idleness or client loss (#2850)

* feat(minimald): end an until_complete box when its entrypoint returns

The host loop now says whether a kill ended it. The actor watches each
launch's loop and, when the entrypoint returned on its own and the spec
sets lifetime = "until_complete", sets the record to exited with the
exit code and records an exited event with reason "exit". The daemon
does this whether or not a client is attached. Such a box raises no
shell-exit prompt (BOX-038). A client that goes away never stops a box
(BOX-015).

* test(minimald): pin box ends on entrypoint return, not client loss

Adds the actor tests for BOX-038 and BOX-015. The shell round trip now
expects no prompt and an exited record. Tests about a box kept after its
shell exits now create an until_stopped box. The rpc shutdown test no
longer answers a prompt.

* fix(minimald): retry the exited-record write when an entrypoint ends

The host and the watch are gone by the time the write runs, so a
single failed write left the box running with nothing in it. Retry it
with a short backoff, and pin the retry with a test seam.

* test(e2e): expect an exited box, not a prompt, when a shell exits

The session-exit prompt is gone: exiting the shell ends the box, which
stays listed as exited. The pty driver no longer answers a prompt. Each
leg that exited a shell now checks the box is exited with code 0, and
destroys it where it used to answer Delete.

* docs(minimald): put the launch-record seam doc back on its module

* test(e2e): resume the exited kept box before the exec into it

An exec runs only in a running box. After the shell exits and the row is
withdrawn, the leg now resumes the box with a pty 'min session attach',
checks that it runs under the same id and that its row is back, and then
runs the exec as before.

* test(minimald): fail when the channel stays open after shell exit

The wait loop ended on a timeout as well as on close, so the test
passed even if the channel never closed. Panic on the timeout.

* fix(minimald): end an exited box before a queued attach or exec

A message queued beside a returned entrypoint ran first and launched
a new host, so the entrypoint ran again and its exit was lost. Now
only a stop or a destroy goes ahead of the exit, and ensure_host
refuses a stopped or exited box.

---------

Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>

* build(deps): bump sysinfo to 0.40

sysinfo 0.40 makes System construction fallible (System::new returns
Result<System, sysinfo::Error>). The three callers keep their 0.39
behaviour on an unsupported platform: minvmd's metrics sample and
minimald's resource-pool probe return None, and minvmd's host capacity
probe reports zero memory.

Unblocks the dependabot cargo group (#2844), which bumped sysinfo
without migrating the callers.

---------

Co-authored-by: Karl Godard <karl@minimal.dev>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review:approved minimal-review approved the current head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

T126 — A host-address box's port that collides on the host is shown at session start and in min ls

1 participant

0