8000 T2 — Stop and remove a box by minimal-foundry[bot] · Pull Request #2961 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

T2 — Stop and remove a box - #2961

Merged
mitodrummer merged 8 commits into
mainfrom
agent/2890-stop-and-remove-a-box
Nov 4, 2026
Merged

mitodrummer merged 8 commits into
mainfrom
agent/2890-stop-and-remove-a-box

Conversation

@minimal-foundry
@minimal-foundry minimal-foundry Bot commented Nov 4, 2026 •
Copy link
Copy Markdown
Contributor

T2 — Stop and remove a box

slice: S0
kind: task
covers: BCLI-059, BCLI-060, BCLI-004
size: M
est: 200 code, 180 tests
executor: agent
difficulty: routine
model: standard
diagnostics: when the box is running and --force is absent, min box rm prints " is running. Stop it with min box stop , or remove it now with min box rm --force." on stderr and exits 2, pinned by rm_running_box_refuses_naming_force
observability: the command's existing tracing span records the verb, the box id it resolved and the outcome
layers:
- client:
- crates/minimal/src/cli.rs: the verb's arguments and help in the one command model
- crates/minimal/src/cmd/mod.rs: dispatches the verb
- crates/minimal/src/cmd/boxes.rs: exports the stop and remove verbs
- crates/minimal/src/cmd/boxes/lifecycle.rs: min box stop [--force] over StopBox and min box rm [--force] over ReapBox
test: cargo nextest run -p minimal box_stop_stops_as_box_013_force_forces
test: cargo nextest run -p minimal box_rm_reaps_as_box_019_force_forces
test: cargo nextest run -p minimal rm_running_box_refuses_naming_force
produces: none
consumes: none
proof: A person stops a box with min box stop and removes it with min box rm, and a running box refuses removal without --force.

Proof

A person runs min box stop <box> to stop a box and keep its record. --force kills its processes at once. The person runs min box rm <box> to remove a stopped or exited box. For a running box without --force, min box rm prints " is running. Stop it with min box stop , or remove it now with min box rm --force." on stderr, exits 2 and leaves the box running. With --force, it stops the box and removes it.

Notes from the agent: crates/minimal/src/cmd/boxes.rs needs no change: it already re-exports everything in lifecycle.rs with pub(crate) use lifecycle::*, so the new cmd_box_stop and cmd_box_rm reach the dispatch through that line. Exit 2: main.rs (outside the layers) maps error types to exit codes. To stay inside the layers, cmd_box_rm prints the refusal to stderr itself and returns minimal::task::TaskExit(2), which main.rs relays as the exit code without printing more. A follow-up can give BoxRunningRefusal its own arm in failure_exit_code in main.rs. The client checks for a running box with the state from the ListBoxes listing before it calls ReapBox. It also maps the daemon's own running-box refusal to the same message, in case the box starts between the two calls. Observability: the root cmd span in cmd/mod.rs now declares verb, box_id and outcome, and the two verbs record them. min box stop --exec (also in BCLI-059) is not part of this task and is not implemented. min box rm does not withdraw a VM host row or release a held name the way min session destroy does. It relies on the daemon's ReapBox for that, as BOX-019 defines the reap. Success lines are "Stopped .", " exited before the stop, so the stop changed nothing." and "Removed ." on stdout. The tests use the in-process minimald test harness. They check the request each verb sends, the state ListBoxes reports after each verb, and the exact refusal text. I did not run just ci or a run of the whole crate.

Tests

line exit tail
cargo nextest run -p minimal box_stop_stops_as_box_013_force_forces 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-5d1e... 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 box_rm_reaps_as_box_019_force_forces 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-b856... 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.
cargo nextest run -p minimal rm_running_box_refuses_naming_force 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-1464... 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.

ted", "cmd/mod.rs::resolve_box_on_any_vm = gated", "cmd/net.rs::cmd_net_forward = gated", "cmd/net.rs::poll_client_for = gated", "cmd/secret.rs::recertify = ungated", "cmd/session.rs::cmd_attach = gated", "cmd/session.rs::cmd_exec = gated", "cmd/session.rs::cmd_session_run = gated", "cmd/session.rs::cmd_session_setup_zed = gated", "dash/rpc.rs::activate = gated", "dash/rpc.rs::connect_missing = gated", "dash/rpc.rs::discover = gated", "diag/net.rs::probe_socket = ungated", "mint.rs::a_refused_client_key_is_replaced_and_registered_before_the_mint = ungated", "mint.rs::a_replaced_client_key_is_revoked_at_the_proxy_after_a_failed_revoke = ungated", "mint.rs::a_replaced_client_key_is_revoked_at_the_proxy_once_for_two_creations = ungated", "mint.rs::a_replaced_client_key_keeps_verifying_until_its_boxes_end = ungated", "mint.rs::a_replaced_client_key_keeps_verifying_until_its_boxes_end = ungated", "mint.rs::a_replaced_client_key_keeps_verifying_until_its_boxes_end = ungated", "mint.rs::a_replacement_past_a_bound_of_keys_in_use_fails_the_creation = ungated", "mint.rs::a_replacement_the_store_does_not_keep_revokes_no_key = ungated", "mint.rs::create = ungated", "mint.rs::local_client_key_at = ungated", "mint.rs::revoke_others = ungated", "mint.rs::store_handle_signed_and_key_registered = ungated", "mint.rs::store_handle_signed_and_key_registered = ungated", "task.rs::arm_task_run_interrupt = ungated", "tests.rs::a_failed_box_reply_forgets_the_box_again = ungated", "tests.rs::a_refused_client_key_forgets_the_box_again = ungated", "tests.rs::an_identity_with_no_root_validity_mints_nothing = ungated", "tests.rs::attach_fall_through_hands_off_the_owning_vm = gated", "tests.rs::attach_refuses_a_box_name_two_vms_know = gated", "tests.rs::exec_fall_through_hands_off_the_owning_vm = gated", "tests.rs::grant_box_registers_its_declared_egress_with_the_proxy = ungated", "tests.rs::grant_box_registers_its_declared_egress_with_the_proxy = ungated", "tests.rs::grant_box_registers_its_declared_egress_with_the_proxy_until_the_creation_fails = ungated", "tests.rs::local_member_is_a_sign_in_reference_expiring_with_the_root = ungated", "tests.rs::mint_and_destroy_append_audit_records = ungated", "tests.rs::selected_client = ungated"]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   0.900s] (608/851) minimal tests::box_name_resolves_vm_without_flag_refuses_a_name_two_vms_hold
        PASS [   0.858s] (609/851) minimal tests::exec_fall_through_hands_off_the_owning_vm
        PASS [   1.585s] (610/851) minimal tests::box_on_stopped_vm_names_start_command
────────────
     Summary [  14.998s] 610/851 tests run: 609 passed, 1 failed, 1 skipped
        FAIL [   0.077s] (607/851) minimal tests::every_daemon_connection_is_classified
warning: 241/851 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

Closes #2890

Out-of-layer edit accepted by the maintainer (2026-11-04): crates/minimal/src/tests.rs holds the in-process minimald test harness that these tests use. The plan should have named it in the layers.

Residuals

`min box stop <box> [--force]` stops a box over StopBox and keeps its
record. `min box rm <box> [--force]` removes a box over ReapBox. A
running box without `--force` is refused on stderr with exit 2, and the
refusal names `--force` (BCLI-059, BCLI-060, BCLI-004).

The command's root span records the verb, the resolved box id and the
outcome.
@minimal-foundry
minimal-foundry Bot requested a review from a team as a code owner November 4, 2026 00:14
@coderabbitai
coderabbitai Bot commented Nov 4, 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: 670473a7-dd9b-4903-8ac1-a598c0976290

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

@minimal-foundry
minimal-foundry Bot commented Nov 4, 2026 •
Copy link
Copy Markdown
Contributor Author

Approved: no findings at 899be48289d3.

Nothing to do.

Verdict APPROVE, posted as a comment: the App cannot review its own pull request; the check and the label carry it.

Clobber: none seen.

@minimal-foundry minimal-foundry Bot left a comment
8000 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: CONDITIONS · 1 condition · the full ruling

min box stop and the running-box refusal of min box rm conform. min box rm does not do the creator's host-side release that min session destroy does. Architecture line 458 makes the type nouns aliases of the min box forms, so the two must end a box the same way. On a LocalVM, a removed box keeps its row, its reserved address and its host_ip name hold with the VM host daemon (networking §7.1 line 244). Make the change in C1. Separately, add daemon_with_boxes to the connect-site inventory in crates/minimal/src/tests.rs, because every_daemon_connection_is_classified fails at this head.

Ruled at 8d5ac60 against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.

The race branch in min box rm matched the daemon's text with no test.
Extract the match into reap_error_is_running and check it against a
real unforced ReapBox of a running box.
@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: CONDITIONS · 2 conditions · the full ruling

min box stop and the running-box refusal of min box rm conform. min box rm stops at the in-VM ReapBox. On a minvmd host, the host row, its address, the host_ip egress and the zone hold stay behind. Networking §7.1 lines 244 and 255 give that withdrawal to the host-side creator, never in-VM minimald. Share the retirement in destroy_box with the rm path, as C1 says. File the exit-code follow-up as an issue, or remove the sentence, as C2 says. Separately, the verify run fails every_daemon_connection_is_classified because of the new daemon_with_boxes connect site.

Ruled at 95fd359 against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on the runtime's review findings, pushed as 95fd3592d674.

line exit tail
cargo nextest run -p minimal box_stop_stops_as_box_013_force_forces 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-83ba... 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 box_rm_reaps_as_box_019_force_forces 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-08f5... 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.
cargo nextest run -p minimal rm_running_box_refuses_naming_force 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-674d... 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.

hecked = ungated", "cmd/mod.rs::resolve_box_on_any_vm = gated", "cmd/net.rs::cmd_net_forward = gated", "cmd/net.rs::poll_client_for = gated", "cmd/secret.rs::recertify = ungated", "cmd/session.rs::cmd_attach = gated", "cmd/session.rs::cmd_exec = gated", "cmd/session.rs::cmd_session_run = gated", "cmd/session.rs::cmd_session_setup_zed = gated", "dash/rpc.rs::activate = gated", "dash/rpc.rs::connect_missing = gated", "dash/rpc.rs::discover = gated", "diag/net.rs::probe_socket = ungated", "mint.rs::a_refused_client_key_is_replaced_and_registered_before_the_mint = ungated", "mint.rs::a_replaced_client_key_is_revoked_at_the_proxy_after_a_failed_revoke = ungated", "mint.rs::a_replaced_client_key_is_revoked_at_the_proxy_once_for_two_creations = ungated", "mint.rs::a_replaced_client_key_keeps_verifying_until_its_boxes_end = ungated", "mint.rs::a_replaced_client_key_keeps_verifying_until_its_boxes_end = ungated", "mint.rs::a_replaced_client_key_keeps_verifying_until_its_boxes_end = ungated", "mint.rs::a_replacement_past_a_bound_of_keys_in_use_fails_the_creation = ungated", "mint.rs::a_replacement_the_store_does_not_keep_revokes_no_key = ungated", "mint.rs::create = ungated", "mint.rs::local_client_key_at = ungated", "mint.rs::revoke_others = ungated", "mint.rs::store_handle_signed_and_key_registered = ungated", "mint.rs::store_handle_signed_and_key_registered = ungated", "task.rs::arm_task_run_interrupt = ungated", "tests.rs::a_failed_box_reply_forgets_the_box_again = ungated", "tests.rs::a_refused_client_key_forgets_the_box_again = ungated", "tests.rs::an_identity_with_no_root_validity_mints_nothing = ungated", "tests.rs::attach_fall_through_hands_off_the_owning_vm = gated", "tests.rs::attach_refuses_a_box_name_two_vms_know = gated", "tests.rs::exec_fall_through_hands_off_the_owning_vm = gated", "tests.rs::grant_box_registers_its_declared_egress_with_the_proxy = ungated", "tests.rs::grant_box_registers_its_declared_egress_with_the_proxy = ungated", "tests.rs::grant_box_registers_its_declared_egress_with_the_proxy_until_the_creation_fails = ungated", "tests.rs::local_member_is_a_sign_in_reference_expiring_with_the_root = ungated", "tests.rs::mint_and_destroy_append_audit_records = ungated", "tests.rs::selected_client = ungated"]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   0.013s] (609/851) minimal tests::exec_stdout_relay_copies_and_detects_broken_pipe
        PASS [   0.681s] (610/851) minimal tests::exec_fall_through_hands_off_the_owning_vm
        PASS [   1.289s] (611/851) minimal tests::box_on_stopped_vm_names_start_command
────────────
     Summary [  13.118s] 611/851 tests run: 610 passed, 1 failed, 1 skipped
        FAIL [   0.062s] (608/851) minimal tests::every_daemon_connection_is_classified
warning: 240/851 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

Review threads the coder judged:

  • 👍 valid — PRRT_kwDOPdfcxM6zY-A3: Moved the match into reap_error_is_running. rm_running_box_refuses_naming_force now sends a real unforced ReapBox of a running box and asserts that the daemon's answer maps to the refusal. A change to the daemon wording now fails the test. (not posted on the thread: not an open thread on the pull request)

A person stops a box with min box stop and removes it with min box rm. A running box refuses removal without --force, also when the daemon gives the running-box answer, and a test now pins that daemon answer.

Notes from the agent: Revise round 1. The match on the daemon's running-box text moved into reap_error_is_running. rm_running_box_refuses_naming_force now sends an unforced ReapBox for a running box to the test daemon and asserts that the helper maps the daemon's answer to the refusal. cargo clippy -p minimal --all-targets printed no warnings.

Read the box record before the reap and, after it, withdraw the row or
release the held name as destroy_box does. Classify the test helper's
daemon connection in the connect-site inventory.
@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

min box rm now withdraws the row or releases the name hold after the reap, as destroy_box does. That closes the open point from the last head. One gap stays on min box stop. A stopped host_ip box keeps its rules in the VM host daemon's cohort union, because only a client retires them. Section 4.3 line 84 admits the cohort by live boxes only, and §5.5 line 183 says a stopped box has no live egress. C1 adds one best-effort retire call after the stop. The gap is filed as #2747, so the merge can go ahead with C1 open.

Ruled at 4229238 against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 2 of 10 on a failed check suite, pushed as 4229238afe1a.

line exit tail
cargo nextest run -p minimal box_stop_stops_as_box_013_force_forces 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-2463... 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 box_rm_reaps_as_box_019_force_forces 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-e6d1... 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.
cargo nextest run -p minimal rm_running_box_refuses_naming_force 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-4060... 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.

dSpan {
            name: "session.message",
            fields: {
                "kind": "GetNetSwitch",
                "parent_span_id": "7d46d5ada0f83a23",
                "span_id": "82049a40bf94d2fa",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetDenyAllOptOut",
                "parent_span_id": "7d46d5ada0f83a23",
                "span_id": "f7cf34e9632bdf73",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "7d46d5ada0f83a23",
                "span_id": "e563a0ff9b1a3ce3",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "7d46d5ada0f83a23",
                "queued_behind": "0",
                "span_id": "e415fc42e0b36a4a",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "7d46d5ada0f83a23",
                "queued_behind": "0",
                "span_id": "bc1fe1a92fdddc45",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "bc1fe1a92fdddc45",
                "span_id": "e081b44f16968afa",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "bc1fe1a92fdddc45",
                "queued_behind": "0",
                "span_id": "5e260df15ee02ac1",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 
────────────
     Summary [  46.822s] 851 tests run: 850 passed, 1 failed, 1 skipped
        FAIL [   3.421s] (851/851) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
error: test run failed

Outside the task's layers::

  • crates/minimal/src/tests.rs

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

Review threads the coder judged:

  • 👍 valid — crates/minimal/src/cmd/boxes/lifecycle.rs:732: rm_listed_box now reads box_addresses with get_session_record before the reap and returns the name and addresses. cmd_box_rm then calls withdraw_box_row and release_held_box_name, as destroy_box does.
  • 👍 valid — crates/minimal/src/cmd/boxes/lifecycle.rs:821: Added "cmd/boxes/lifecycle.rs::daemon_with_boxes = ungated" to the connect-site inventory in tests.rs. every_daemon_connection_is_classified passes.
  • 👎 invalid — crates/minimal/src/cmd/boxes/lifecycle.rs:694: find_listed_box in show.rs is the shared name resolution rule. min box show uses it too, and a name resolves to its running box, else to the newest one. If stop and rm use a different rule, one name resolves to two boxes. A change to that rule belongs in show.rs, which this task's layers do not name
  • 👍 valid — crates/minimal/src/cmd/boxes/lifecycle.rs:732: After a successful reap, cmd_box_rm now does the destroy_box retirement: withdraw_box_row and release_held_box_name. It uses the box_addresses that get_session_record read before the reap.
  • 👍 valid — crates/minimal/src/cmd/boxes/lifecycle.rs:659: No code change. The account for this round names no follow-up for failure_exit_code, so the pull request body drops the sentence. The exit 2 stays as it is.
  • 👍 valid — crates/minimal/src/cmd/boxes/lifecycle.rs:821: Same fix as the other inventory thread: daemon_with_boxes is now in the connect-site inventory, and every_daemon_connection_is_classified passes.

A person stops a box with min box stop <box> and removes it with min box rm <box>. A running box refuses removal without --force, on stderr with exit 2. After a removal, the row or name hold that the box left on a VM host daemon is retired, the same as min session destroy does.

Notes from the agent: Revise round 2. The tests check failed on every_daemon_connection_is_classified, because the test helper daemon_with_boxes is a new Client::connect( site. I added one inventory entry to crates/minimal/src/tests.rs. That file is outside the task's layers, but the crate-wide guard test requires the entry. The ci-linux-native-success failure is only the lane aggregator of that job. rm_listed_box now returns the reaped box's id, name and addresses, and cmd_box_rm calls withdraw_box_row and release_held_box_name after it. The tests drive rm_listed_box against the harness daemon, so they do not reach the VM host withdrawal itself. The exit-2 refusal goes through TaskExit as before. I do not name any follow-up work for failure_exit_code this round, because no issue for it exists.

@mitodrummer
Copy link
Copy Markdown
Member

/revise

From the maintainer's pre-merge review at 4229238a. The task's layers now include crates/minimal/src/main.rs and crates/minimal/src/tests.rs.

  1. Merge main and fix the build (blocker). T5 — Versioned JSON objects and errors, secrets as references #2960 (T5) added command_verb() in crates/minimal/src/cmd/mod.rs. Its match on BoxCommand lists only Spec, Audit, List and Show, with no wildcard. After a merge of main, Stop and Rm make it non-exhaustive (E0004). Merge main and add Stop → "box stop" and Rm → "box rm". Drop this PR's duplicate verb/box_id/outcome fields on the root span, and drop record_box_verb. main now owns those fields: verb from command_verb, outcome from run_in_root, and box_id through output::record_box_id.

  2. Exit 2 through failure_exit_code, not TaskExit(2). Give BoxRunningRefusal its own arm in failure_exit_code in main.rs, as HeldNameRefusal has. cmd_box_rm returns the refusal as an error. Then a machine-mode run gets the min/v1/error object, because answer_failure handles TaskExit before machine mode. Add a test that the running-box refusal exits 2 through failure_exit_code, and replace the assert_eq!(BoxRunningRefusal::EXIT_CODE, 2) self-comparison.

  3. Don't skip cleanup on a closed stdout. writeln!(out, "Removed {target}.")? (lifecycle.rs ~733) runs after the reap. On a closed pipe it returns early, so withdraw_box_row and release_held_box_name are skipped for a box that was removed. Write the line after the cleanup, or ignore that write's error.

  4. Pin --force on stop end to end. box_stop_stops_as_box_013_force_forces checks force only on the request struct, and it asserts stopped, which a graceful stop also gives. Make the forced half observable through the daemon. Also add a case for the already exited branch of stop_listed_box.

  5. Refresh the PR body. Two sentences are no longer true:

    • "A follow-up can give BoxRunningRefusal its own arm…" (item 2 does it now);
    • "min box rm does not withdraw a VM host row or release a held name…" (4229238a does both).

    Keep the Residuals section (BCLI: min box stop retires the box's host_ip egress on the VM host (T2 residual, arch C1) #2982).

The running-box refusal now ends with exit 2 from failure_exit_code, so a
machine-mode run gets the min/v1/error object. The root span owns verb,
box_id and outcome: box stop and box rm join command_verb and record the
resolved id through output::record_box_id. min box rm prints its line after
the row and name cleanup, so a closed stdout cannot skip it. min box stop
retires a host_ip box's egress rules on the VM host daemon. The stop test
reads the daemon's log to prove the forced stop, and covers the line for a
box that exited before the stop.
@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. min box stop now retires a host_ip box's egress on the VM host daemon, and that meets the open condition from 4229238a (§5.5 line 183). The running-box refusal of min box rm now ends through failure_exit_code with exit 2, so a machine mode gets the min/v1/error object (lines 363 and 383). Close #2982 with this merge.

Ruled at 2f568b7 against gominimal/arch@7a86ec7bb3c0 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: 8 unresolved threads — minimal-arch[bot] crates/minimal/src/cmd/boxes/lifecycle.rs, minimal-arch[bot] crates/minimal/src/cmd/boxes/lifecycle.rs:805, minimal-foundry[bot] crates/minimal/src/cmd/boxes/lifecycle.rs, minimal-arch[bot] crates/minimal/src/cmd/boxes/lifecycle.rs, minimal-arch[bot] crates/minimal/src/cmd/boxes/lifecycle.rs, …. No findings.

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

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 3 of 10 on a /revise note, pushed as 2f568b7232e2.

line exit tail
cargo nextest run -p minimal box_stop_stops_as_box_013_force_forces 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-f2b0... 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 box_rm_reaps_as_box_019_force_forces 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-2f5a... 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.
cargo nextest run -p minimal rm_running_box_refuses_naming_force 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-ed9f... 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.

dSpan {
            name: "session.message",
            fields: {
                "kind": "GetNetSwitch",
                "parent_span_id": "235dd99b48b84323",
                "span_id": "554cc7defdd4b24e",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetDenyAllOptOut",
                "parent_span_id": "235dd99b48b84323",
                "span_id": "5e93176c61ee47d4",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "235dd99b48b84323",
                "span_id": "0e7ed7a7559823d9",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "235dd99b48b84323",
                "queued_behind": "0",
                "span_id": "7979ace3d2355561",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "235dd99b48b84323",
                "queued_behind": "0",
                "span_id": "11598102a8704fee",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "11598102a8704fee",
                "span_id": "8f9a686d84576ff2",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "11598102a8704fee",
                "queued_behind": "0",
                "span_id": "b035629cdc11b3c1",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 
────────────
     Summary [  41.301s] 857 tests run: 856 passed, 1 failed, 1 skipped
        FAIL [   3.019s] (857/857) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
error: test run failed

Review threads the coder judged:

  • 👍 valid — crates/minimal/src/cmd/boxes/lifecycle.rs: rm_listed_box no longer writes to stdout. cmd_box_rm prints Removed <box>. after withdraw_box_row and release_held_box_name, so a closed stdout cannot skip the cleanup. Hook warnings go to stderr before that.
  • 👍 valid — crates/minimal/src/cmd/boxes/lifecycle.rs: stop_listed_box now returns the stopped box's id. cmd_box_stop then calls host_ip_box_with_vm_host(vm_host_control_sock(..), id, None, false). The row and the name hold stay.

A person stops a box with min box stop <box> (graceful, or forced with --force) and removes it with min box rm <box>. min box rm of a running box without --force prints "error: is running. Stop it with min box stop , or remove it now with min box rm --force." on stderr and exits 2. Under a machine output mode it prints the min/v1/error object and also exits 2.

Notes from the agent: Revise round 3. Main was already merged at d20ec86, so no merge was necessary. Item 1: Stop and Rm are now in command_verb ("box stop", "box rm"). The duplicate verb, box_id and outcome fields on the root span are gone, record_box_verb is gone, and both verbs record the resolved id through output::record_box_id. Item 2: BoxRunningRefusal is now public and re-exported. It has its own arm in failure_exit_code (exit 2), and cmd_box_rm returns it as an error, with no TaskExit. The new test rm_running_box_refuses_naming_force_with_exit_2 in main.rs checks exit 2 in text mode and in machine mode, and it pins the stderr line. The EXIT_CODE self-comparison is gone. The text-mode line now has the shared error: prefix in front of the diagnostics wording. Item 3: rm_listed_box no longer writes to stdout. cmd_box_rm prints Removed <box>. after withdraw_box_row and release_held_box_name. Item 4: the in-process harness launches no box process, so a forced stop and a graceful stop end the same way in the record. The test proves the forced half from the daemon's own session stopped log line (force=true for the forced box, force=false for the graceful one), read through minimald::test_harness::captured_log(). The daemon's already_exited answer needs a test-only race seam in minimald, and a minimal test cannot reach that seam. So I moved the line choice into write_stop_line and test the already_exited branch with a StopBoxResponse that has already_exited: true. Item 5: I cannot edit the pull request body. In the…[truncated]

@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; @mitodrummer closed the last one. The review of 2f568b7232e2 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.

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

Reviewed at 2f568b72. A whole-PR review at 4229238a found one blocker (the build broke against main after T5), and this round fixes it.

  • Build: main is merged, and command_verb has Stop → "box stop" and Rm → "box rm".
  • Exit code: the running-box refusal goes through failure_exit_code (exit 2), replacing TaskExit(2).
  • Cleanup: withdraw_box_row and release_held_box_name run before the stdout line.
  • Stop: --force reaches StopBox. The record stays after a stop (BOX-018).
  • Rm: the reap's running refusal and the listing check map to the same message.
  • Residuals: #2982 (host_ip retire after a stop). The stopped-box exit code and wording are noted on T33 #2923.
  • Follow-up: the forced half of box_stop_stops_as_box_013_force_forces still proves force only on the request.

@minimal-foundry minimal-foundry Bot added the review:approved minimal-review approved the current head label Nov 4, 2026
@mitodrummer
mitodrummer added this pull request to the merge queue Nov 4, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 4, 2026
@mitodrummer
Copy link
Copy Markdown
Member

/revise

The merge queue removed this PR at 04:45 (failed_checks). Merged with main after T1 (#2963, 04:39), minimal does not compile:

error[E0061]: this function takes 4 arguments but 2 arguments were supplied
   --> crates/minimal/src/cmd/boxes/lifecycle.rs:612:14
   --> crates/minimal/src/cmd/boxes/lifecycle.rs:686:14
note: function defined here: crates/minimal/src/cmd/boxes/show.rs:58 (find_listed_box)

T1 widened find_listed_box in cmd/boxes/show.rs to 4 arguments. Merge main and update both call sites in lifecycle.rs (stop_listed_box and rm_listed_box) to the new signature, with the same host and lookup semantics T1's min box show uses. Make sure cargo clippy -p minimal and the three named tests pass on the merged tree.

T1 widened find_listed_box to take the host and the not-found remedy.
min box stop and min box rm now look the box up on the selected host,
as min box show does, and their refusal names the host and the verb.
@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 compare since the PASS on 2f568b7 is one branch commit and a merge from main. The commit makes min box stop and min box rm look the box up on the selected host, as min box show does. Line 389 and line 353 of architecture.md address a box on a host, so this conforms. The stop, reap, --force refusal and exit 2 do not change. The residual #2982 stays open.

Ruled at 899be48 against gominimal/arch@7a86ec7bb3c0 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 — approved

Verdict APPROVE — No findings.

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

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 4 of 10 on a /revise note, pushed as 899be48289d3.

line exit tail
cargo nextest run -p minimal box_stop_stops_as_box_013_force_forces 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-80a9... 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 box_rm_reaps_as_box_019_force_forces 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-5c0a... 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.
cargo nextest run -p minimal rm_running_box_refuses_naming_force 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-d4cc... 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 graph 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.

dSpan {
            name: "session.message",
            fields: {
                "kind": "GetNetSwitch",
                "parent_span_id": "c3cd88c276c6196a",
                "span_id": "f2bf793af4a71b31",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetDenyAllOptOut",
                "parent_span_id": "c3cd88c276c6196a",
                "span_id": "aaf8967bbcbc3b60",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "c3cd88c276c6196a",
                "span_id": "e1a7646bec7822a2",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "c3cd88c276c6196a",
                "queued_behind": "0",
                "span_id": "fab3e3a0273ef74e",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "c3cd88c276c6196a",
                "queued_behind": "0",
                "span_id": "ff2dce0a805ef351",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "ff2dce0a805ef351",
                "span_id": "eb830fed93294913",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "ff2dce0a805ef351",
                "queued_behind": "0",
                "span_id": "ac3a791bb2227b26",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 
────────────
     Summary [  38.997s] 861 tests run: 860 passed, 1 failed, 1 skipped
        FAIL [   2.930s] (861/861) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
error: test run failed

A person stops a box with min box stop and removes it with min box rm. A running box refuses removal without --force. On the tree merged with main, both verbs look the box up on the selected host, as min box show does.

Notes from the agent: The merge with main (2b46c80) was already on the branch and had no conflicts. stop_listed_box and rm_listed_box now take a BoxLookup (host and not-found remedy). The callers build it from local_hosts with the selected host, the same as cmd_box_show. With one host the remedy is not_found_remedy's text. With several hosts the remedy names the verb that ran (min box stop or min box rm) with --vm <name>, not min box show. A new unit test, lookup_refusal_names_host_and_verb, pins both texts. Known gap: find_listed_box in show.rs (outside this task's layers) still says "Run min box show with more of the id." for an ambiguous id start, also when stop or rm calls it. cargo clippy -p minimal --all-targets is clean. I did not run just ci or clippy-strict.

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

Re-approved at 899be482. It merges main with T1 (#2963) and passes host and remedy to find_listed_box in stop_listed_box and rm_listed_box, which fixes the E0061 that ejected the merge-queue entry at 04:45. Every lane is green on this head, and arch passes.

@mitodrummer
mitodrummer added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit 5ac253d Nov 4, 2026
36 checks passed
@mitodrummer
mitodrummer deleted the agent/2890-stop-and-remove-a-box branch November 4, 2026 06:17
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.

T2 — Stop and remove a box

1 participant

0