8000 feat(minimald): refuse an activation whose declared port something else holds (T100) by minimal-foundry[bot] · Pull Request #2725 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

feat(minimald): refuse an activation whose declared port something else holds (T100) - #2725

Merged
norrietaylor merged 6 commits into
mainfrom
agent/2703-activation-refuses-a-declared-port-that-somethin
Oct 29, 2026
Merged

norrietaylor merged 6 commits into
mainfrom
agent/2703-activation-refuses-a-declared-port-that-somethin

Conversation

@minimal-foundry
Copy link
Copy Markdown
Contributor

T100 — Activation refuses a declared port that somethin 8000 g else holds

slice: S0
kind: task
covers: NET-144
size: M
est: 300 code, 180 tests
executor: agent
difficulty: needs care
model: standard
diagnostics: the refusal itself is what a person reads: min session activate exits non-zero and prints "Port is already in use on this machine by box ." or, when no box holds it, "Port is already in use on this machine by a program outside Minimal.", then "Choose another host port in --ingress (for example --ingress <port+1>:) or in [network] ingress in minimal.toml.", pinned by activation_refused_when_declared_port_is_held and activate_refusal_names_port_and_holder
observability: one warn line in the daemon log per refused activation with the session id, the port, the address and the holder (box:<name> or host)
layers:
- daemon:
- crates/minimald/src/box_actor/setup.rs: finalize binds the declared ingress forwards before it writes the record Active, and a bind that fails fails the finalize, so the client's activate cleanup destroys the box; today the forwards bind only at the first attach and the box activates with the port dead
- crates/minimald/src/net/gvproxy_network.rs: finish_own_ip_attach returns the failed port and its holder instead of a bare io error
- crates/minimald/src/net/policy.rs: apply_ingress names the holder: the box whose publish stands at that address and port, read from the hostname registry, or the host when none does
- crates/minimald/src/net/dns.rs: a declared port that another box already holds at the shared 127.0.0.1 fallback is a refusal at activation, not a yielded port; a host-address box sharing its node's address keeps the shared-address rule
- crates/minimald/src/box_actor/tests.rs: a box whose declared port is held by a host listener, and one whose port another box holds at the fallback, both fail activation and leave no record
test: cargo nextest run -p minimald activation_refused_when_declared_port_is_held
- client:
- crates/minimald-rpc/src/lib.rs: a typed activation refusal carrying the port and the holder
- crates/minimal/src/cmd/session.rs: prints the refusal text in the diagnostics line and exits non-zero
test: cargo nextest run -p minimal activate_refusal_names_port_and_holder
produces:
- crates/minimald-rpc/src/lib.rs
consumes: none
depends on:
- blocked by #2688
proof: Activating a box whose --ingress port another box or a host program already holds fails with the port and the holder named, and min ls shows no box.

Proof

A box activated with an --ingress host port that a host program holds, or that another box holds at the shared 127.0.0.1 fallback, fails min session activate with a non-zero exit. It prints "Port is already in use on this machine by box ." or "... by a program outside Minimal.", then the --ingress remedy line. The daemon refuses before the record goes Active, and the activate cleanup destroys the box, so min ls shows no box. The daemon tests prove the destroy-and-list part over the RPC, not through the CLI end to end.

Notes from the agent: 1. NET-144 is not in .implement/spec.md. I worked from the task text only.
2. Finalize does not bind the real forwards before Active. The forwards need the box's switch lease, and that exists only after a launch. Finalize keeps the existing bind probe for host programs and adds the box-holder check at the fallback. Both fail the finalize before the Active write. With activate hooks, the hook launch binds the forwards, and a taken port fails with the same typed refusal.
3. Fallback rule: another box that holds the port at 127.0.0.1 refuses the activation only when the range verdict has landed (Absent or Present). Under a Pending verdict the interim is a short stop before the box's own hand, so the port is yielded as before. Without this, handed_finalizes_share_one_verdict_deadline fails, and a normal start under a pending verdict breaks. The new LoopbackLeaseBook::range_verdict in dns.rs gives finalize the verdict.
4. apply_ingress keeps its 6-argument signature because crates/minimald/tests/netns_root_integration.rs (outside the layers) calls it. The holder-naming version is the new apply_ingress_naming_holders, which the attach path uses. apply_ingress names the host as the holder.
5. The refusal travels as the daemon's error text. PortInUseRefusal formats the text and parses it back with find_in, so the two cannot drift. The client replaces the whole error chain with that text, only in cmd_activate (ses…[truncated]

Tests

line exit tail
cargo nextest run -p minimald activation_refused_when_declared_port_is_held 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-28b3... 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_refusal_names_port_and_holder 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-eff4... 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

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.

arent_span_id": "f769aec4d8a63b7a",
                "span_id": "b71710c4d3c97075",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetDenyAllOptOut",
                "parent_span_id": "f769aec4d8a63b7a",
                "span_id": "1c383c480f18e9db",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "f769aec4d8a63b7a",
                "span_id": "6ab4c9c0a6f80207",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "f769aec4d8a63b7a",
                "queued_behind": "0",
                "span_id": "733ccc32fde84538",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "f769aec4d8a63b7a",
                "queued_behind": "0",
                "span_id": "aa4a8a5b55762398",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "aa4a8a5b55762398",
                "span_id": "33179e2d6f6ddee6",
                "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.289s] ( 823/2031) minimald box_actor::tests::a_box_destroyed_between_enumeration_and_grant_is_released_once
        PASS [   2.313s] ( 824/2031) minimald box_actor::tests::a_box_destroyed_inside_the_landing_window_is_not_resurrected
        PASS [   2.309s] ( 825/2031) minimald box_actor::tests::a_box_holding_a_host_row_refuses_remint_after_its_host_loop_ends
────────────
     Summary [  36.476s] 825/2031 tests run: 824 passed, 1 failed, 20 skipped
        FAIL [   2.839s] ( 822/2031) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 1206/2031 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 #2703

…T100)

Finalize refuses a declared ingress host port that a program outside
Minimal holds, or that another box holds at the 127.0.0.1 fallback
once a landed range verdict keeps the box there. The refusal is a
typed PortInUseRefusal that names the port and its holder, and the
daemon logs one warn line with the session, port, address and holder.
min session activate ends with the daemon's refusal text alone when a
declared host port is already in use: the port, its holder and the
--ingress remedy, in place of the RPC context around it.
@coderabbitai
coderabbitai Bot commented Oct 28, 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: 1f76c8f9-93cc-4a85-8fa9-8a4b92f34703

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

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

@minimal-foundry
minimal-foundry Bot commented Oct 28, 2026 •
Copy link
Copy Markdown
Contributor Author

Reviewed: 1 finding, none blocking at 3e62ed645b55.

Address the comments on the diff.

Clobber: none seen.

@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 minimal-foundry Bot added the review:approved minimal-review approved the current head label Oct 28, 2026
@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 · 3 conditions · the full ruling

The fallback refusal conforms to §7.1. The first box keeps the port, and the later box gets a surfaced error (lines 249 and 258). The holder text is wrong. Every holder that is not a box in this daemon's registry prints as "a program outside Minimal". Line 258 says the daemon's own proxy and answerer hold ports on that address, and lines 256 and 258 add other nodes' boxes. C1 fixes the text. C2 and C3 file the two cases that the PR notes leave undone, the VM-backed host and the Pending verdict, as residuals with issues.

Ruled at e62e585 against gominimal/arch@109541a48c31 by claude-opus-5-5.

Comment thread crates/minimald-rpc/src/lib.rs Resolved
Comment thread crates/minimald/src/box_actor/setup.rs Resolved
Comment thread crates/minimald/src/box_actor/setup.rs Resolved
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on a failed check suite: the coder changed nothing. Revise round 1: I changed nothing. The directive lists no review threads. Only two checks failed: cargo-deny and its aggregator ci-success. cargo-deny fails on a new RustSec advisory, 'Exponential event growth with ENABLE_WIKILINKS', against pulldown-cmark 0.13.4 in Cargo.lock. This branch does not change Cargo.lock or deny.toml, so the same advisory fails main too. The fix is a pulldown-cmark bump in Cargo.lock or an ignore entry in deny.toml. Both files are outside this task's layers, so this needs a separate pull request on main. After that lands, a merge of main will turn this pull request green. The log also shows 'override toolchain ... is not installed'. This branch does not change rust-toolchain.toml, and the advisories step still ran and reported its result after that line.

Copy link
Copy Markdown
Member

/revise Fix arch-review condition C1 at e62e585 (arch_rulings 516). Change nothing else.

  • In crates/minimald-rpc/src/lib.rs, PORT_IN_USE_HOST becomes "another program on this machine." in place of "a program outside Minimal.". PortHolder::Host means only that no box in this daemon's hostname registry holds the port (crates/minimald/src/net/policy.rs port_holder()); the daemon's own :7654 proxy on 127.0.0.1 and a co-resident node's box also arrive as Host.
  • Update the doc comment on PortHolder::Host to say the same.
  • Update the pinned text in crates/minimal/src/cmd/session.rs activate_refusal_names_port_and_holder, in the apply_ingress test in crates/minimald/src/net/policy.rs (the "Port 18999 is already in use ..." assertion), and in the PR body's diagnostics line.
  • Keep the log label host.
  • C2 and C3 (residual issues) are with the owner; do not file issues.

Generated by Claude Code

No box in the daemon's hostname registry holding the port does not prove
the holder is outside Minimal: the daemon's own proxy and a co-resident
node's box also arrive as the host holder. Arch-review condition C1.
@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 meets the baseline's condition C1. When no box in this daemon's registry holds the port, the refusal now says "another program on this machine". Section 7.1 lines 256 and 258 say that the daemon's own listeners and the boxes of a co-resident node also occupy the shared address. The new text is true for each of these causes. The compare touches nothing else, so the earlier ruling stands for the rest of the change. NET-144 conforms.

Ruled at 8ed2d67 against gominimal/arch@109541a48c31 by claude-opus-5-5.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Revise round 1 ran against ci-success, cargo-deny and left it red; a coder round cannot clear it, a person should look. No round was started for the failed suite on 8ed2d6733989.

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

APPROVE withheld: 3 unresolved threads — minimal-arch[bot] crates/minimald-rpc/src/lib.rs, minimal-arch[bot] crates/minimald/src/box_actor/setup.rs:579, minimal-arch[bot] crates/minimald/src/box_actor/setup.rs:596.

minimal-review — advisory review

Verdict COMMENT — No findings.

Panel: 2 of 3 personas ran (security, supply-chain); did not complete: correctness.

@minimal-foundry minimal-foundry Bot removed the review:approved minimal-review approved the current head label Oct 28, 2026
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 2 of 10 on a /revise note, pushed as 8ed2d6733989.

line exit tail
cargo nextest run -p minimald activation_refused_when_declared_port_is_held 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-61e5... 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_refusal_names_port_and_holder 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-ccc6... 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

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.

_span_id": "b79ae4b463b53c2f",
                "span_id": "4c1047354659290a",
                "trace_id": "0
8BD0
af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetDenyAllOptOut",
                "parent_span_id": "b79ae4b463b53c2f",
                "span_id": "55587ad22bcdd50f",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "b79ae4b463b53c2f",
                "span_id": "154905e170c02cb2",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "b79ae4b463b53c2f",
                "queued_behind": "0",
                "span_id": "23d427405ec7a3bc",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "b79ae4b463b53c2f",
                "queued_behind": "0",
                "span_id": "744cdcc22659a5a8",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "744cdcc22659a5a8",
                "span_id": "305207553ef0bc05",
                "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.277s] ( 826/2031) minimald box_actor::tests::a_box_nobody_handed_an_address_publishes_at_the_answerers_grant
        PASS [   2.337s] ( 827/2031) minimald box_actor::tests::a_box_re_registered_inside_the_landing_window_keeps_its_grant
        PASS [   2.646s] ( 828/2031) minimald box_actor::tests::a_destroy_queued_behind_a_waiting_finalize_runs_after_the_promotion
────────────
     Summary [  34.826s] 828/2031 tests run: 827 passed, 1 failed, 20 skipped
        FAIL [   2.917s] ( 825/2031) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 1203/2031 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 — crates/minimald-rpc/src/lib.rs: PORT_IN_USE_HOST is now "another program on this machine.", the PortHolder::Host doc says only that no box in the registry holds it, and both pinned tests are updated.
  • 👎 invalid — crates/minimald/src/box_actor/setup.rs:579: The owner holds this residual (the round note says C2 is with the owner, and no issue is to be filed). This round changes no code for it. It is not a judgement that the gap is absent.
  • 👎 invalid — crates/minimald/src/box_actor/setup.rs:596: The owner holds this residual (the round note says C3 is with the owner, and no issue is to be filed). This round changes no code for it. It is not a judgement that the window is absent.

Activating a box whose --ingress port another box or a host program already holds fails with the port and the holder named, and min ls shows no box. When no box holds the port, the refusal now reads "Port is already in use on this machine by another program on this machine."

Notes from the agent: Round 2 addresses only C1, as the note asks. PORT_IN_USE_HOST is now "another program on this machine.", the PortHolder::Host doc comment says what the code knows, and the pinned text in session.rs and the policy.rs apply_ingress test is updated. The log label stays host. The PR body's diagnostics line still quotes the old text, and the runner or the owner must update it: I cannot edit the PR body. The full first line now repeats "on this machine" ("...in use on this machine by another program on this machine."). That is the wording the note fixes, and I shipped it as written. A /// comment on the box_actor test at crates/minimald/src/box_actor/tests.rs:3536 still says "a program outside Minimal". It is not printed text, and the note says to change nothing else, so I left it. The cargo-deny failure is a RUSTSEC advisory in a dependency, outside this task. I did not change it. C2 and C3 stay with the owner, and I filed no issues.

Copy link
Copy Markdown
Member

/revise Two things, in order. Change nothing else.

  1. Merge origin/main into this branch. main carries build(deps): pulldown-cmark 0.13.5, for RUSTSEC-2026-0414 #2726, which fixes the cargo-deny advisory that fails cargo-deny and ci-success on 8ed2d67. Resolve any conflict without changing behaviour.
  2. Fix arch conditions C2 and C3 in this PR (arch_rulings 516). The owner chose in-PR fixes over residual issues.
    • C2: on a VM-backed host host_ports_in_use() returns nothing (control.host_unbinds_declared_forwards()), so finalize writes Active and publishes the name before any forward binds, and a host program holding the declared port is found only at the first attach. Bind the declared forwards at publish on a VM-backed host (bind-at-publish), so a held port refuses finalize with the same holder text as the native path.
    • C3: crates/minimald/src/box_actor/setup.rs skips fallback_port_refusal() while range_verdict() is RangeVerdict::Pending, so a box finalized then at 127.0.0.1 keeps the fallback with its declared port dead and is never refused if the verdict later lands Absent. Apply the refusal when the verdict lands: re-check the boxes published on the fallback during the pending window and refuse (or report) each one whose declared port is held.
    • Add a test for each, and keep the C1 holder text as it is.
      Keep every commit message line at or under 100 characters.

Generated by Claude Code

On a VM-backed host the daemon cannot probe the host's ports, so a box
went Active with a held declared port and failed only at its first
attach. Finalize now binds the declared forwards toward the handed
switch address before the Active write. A held port fails with the same
refusal and holder text as the native probe. The first attach takes the
host's "already bound" answer as the box's own forward.

A box that finalizes at the 127.0.0.1 fallback under a pending range
verdict still yields a port another box holds there. A watcher now
waits for the verdict to land, and an abs
10000
ent landing reports the held
port, the address and the holder in the daemon log.
@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. The new commit binds the declared forwards at publish on a VM-backed host. A held port now fails the finalize before the box goes Active, as line 249 requires. The "already running" answer is read as the box's own forward only behind the host gate, and yielded ports are skipped first. The absent-landing watcher adds a log line and changes no decision.

Ruled at 3e62ed6 against gominimal/arch@109541a48c31 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 — advisory review

Verdict COMMENT — 1 finding, 1 inline.

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

Comment thread crates/minimald/src/box_actor/setup.rs Resolved
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

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

line exit tail
cargo nextest run -p minimald activation_refused_when_declared_port_is_held 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-d6c7... 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_refusal_names_port_and_holder 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-0681... 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

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.

_span_id": "92065b0598b8923e",
                "span_id": "c9691536bb03cd0d",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetDenyAllOptOut",
                "parent_span_id": "92065b0598b8923e",
                "span_id": "0744e2f182ecdd5e",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "92065b0598b8923e",
                "span_id": "b585ea4d0f9a698c",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "92065b0598b8923e",
                "queued_behind": "0",
                "span_id": "2227399385aa0c61",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "92065b0598b8923e",
                "queued_behind": "0",
                "span_id": "10d280352501b483",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "10d280352501b483",
                "span_id": "bffbf016be3b70e7",
                "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.332s] ( 831/2047) minimald box_actor::tests::a_box_re_registered_inside_the_landing_window_keeps_its_grant
        PASS [   2.347s] ( 832/2047) minimald box_actor::tests::a_box_nobody_handed_an_address_publishes_at_the_answerers_grant
        PASS [   2.707s] ( 833/2047) minimald box_actor::tests::a_destroy_queued_behind_a_waiting_finalize_runs_after_the_promotion
────────────
     Summary [  37.713s] 833/2047 tests run: 832 passed, 1 failed, 20 skipped
        FAIL [   3.234s] ( 830/2047) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 1214/2047 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

Activating a box whose --ingress port another box or a host program already holds fails with the port and the holder named, and min ls shows no box. This now also holds on a VM-backed host, because finalize binds the declared forwards before the box goes Active. A box that finalized at 127.0.0.1 while the range verdict was pending gets a daemon log report with the port, the address and the holding box if the verdict then lands absent.

Notes from the agent: Item 1 (merge origin/main): already done on this branch by bac3f1d, which includes #2726. No conflicts, no new merge commit.

C2: on a VM-backed host, finalize now calls policy::bind_declared_at_publish before the Active write. It posts the real exposes toward the switch_address the host handed in record.box_addresses. A held port fails with the same PortInUseRefusal text and holder as the native probe. The forwards stay on the host, which unbinds them when it withdraws the box row. On a VM-backed host the first attach now takes the switch's 'proxy already running' answer as the box's own forward. minvmd's gate sends that answer (forward_revoke::held_answer) for a listener that the same address already holds. A VM-backed box with no box_addresses has no switch address, so its first attach still binds the forwards, as before. The finalize path through a vsock control channel has no test, because TestServer only spawns a local switch. The tests drive the VM branch through apply_declared with host_holds_listeners=true over a fake unix forwarder.

C3: I chose report, not refuse, which the directive allows. The box is already Active when the verdict lands, and a refusal there would destroy a running box. Under a pending verdict, finalize starts a detached watcher (LoopbackLeaseBook::await_landing). On an absent landing, the watcher re-reads fallback_port_refusal. If the box still holds a refused port, it writes one warn line with action=host-port-in-use, session, port, address=127.0.0.1 and holder=box:<na…[truncated]

@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 at 3e62ed64. I read the daemon and client changes against T100's plan block and arch C1–C3 (arch_rulings 516). Finalize now binds the declared ingress forwards before the record goes Active, and a held port fails activation with a typed refusal naming the port and its holder; the client prints it and exits non-zero. A port another box holds at the 127.0.0.1 fallback is a refusal, not a yielded port. C1: PortHolder::Host reads "another program on this machine." (minimald-rpc PORT_IN_USE_HOST), with the pinned texts updated. C2: on a VM-backed host bind_declared_at_publish binds the forwards at publish, so a held port refuses finalize with the same holder text. C3: a box finalized at the fallback under a pending range verdict is watched (watch_fallback_landing); when the verdict lands absent the conflict is reported instead of passing silently. Main is merged in. No findings.


Generated by Claude Code

@norrietaylor norrietaylor changed the title T100 — Activation refuses a declared port that something else holds feat(minimald): refuse an activation whose declared port something else holds (T100) Oct 29, 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 approval at 3e62ed6: every merge gate passes at this head (arch PASS at 3e62ed6, zero open threads, every lane aggregator green). Squash title feat(minimald): refuse an activation whose declared port something else holds (T100) and the squashed message pass commitlint. Queued for squash merge.


Generated by Claude Code

@norrietaylor
norrietaylor added this pull request to the merge queue Oct 29, 2026
Merged via the queue into main with commit 703bb7f Oct 29, 2026
37 checks passed
@norrietaylor
norrietaylor deleted the agent/2703-activation-refuses-a-declared-port-that-somethin branch October 29, 2026 00:38
norrietaylor pushed a commit that referenced this pull request Nov 4, 2026
…e holds (#2725)

T100. Finalize refuses a declared ingress host port that a program outside Minimal holds,
or that another box holds at the 127.0.0.1 fallback once a landed range verdict keeps the
box there. The typed PortInUseRefusal names the port and its holder, the daemon logs one
warn line, and min session activate ends with that refusal text alone. An unknown holder
is named as another program. On a VM-backed host, finalize binds the declared forwards
toward the handed switch address before the Active write, and the first attach takes the
host's "already bound" answer as the box's own forward. A box that finalizes at the
fallback under a pending verdict gets a watcher that reports an absent landing.

Backport of #2725, folded from #2738.

Adapted to the line:
- The finalize code lives in session.rs, not box_actor/setup.rs, and registry_name is the
  session module's own helper. In the CLI, activate_refusal applies to activate_session.
- boxes:: becomes sessions:: and switch::BoxGate becomes switch::SessionGate.
- The fallback watcher spawns with tokio::spawn under the same root span.
- OwnAddressReporter::registry comes without NET-129's dynamic-hold tracking. The attach
  path's holder lookup needs only the accessor.
- The line's DestroySessionRequest has no only_if_unfinalized, so the refusal tests assert
  the refused box is listed and not Active, destroy it plainly, and assert no record stays.

(cherry picked from commit 703bb7f)
norrietaylor pushed a commit that referenced this pull request Nov 5, 2026
…e holds (#2725)

T100. Finalize refuses a declared ingress host port that a program outside Minimal holds,
or that another box holds at the 127.0.0.1 fallback once a landed range verdict keeps the
box there. The typed PortInUseRefusal names the port and its holder, the daemon logs one
warn line, and min session activate ends with that refusal text alone. An unknown holder
is named as another program. On a VM-backed host, finalize binds the declared forwards
toward the handed switch address before the Active write, and the first attach takes the
host's "already bound" answer as the box's own forward. A box that finalizes at the
fallback under a pending verdict gets a watcher that reports an absent landing.

Backport of #2725, folded from #2738.

Adapted to the line:
- The finalize code lives in session.rs, not box_actor/setup.rs, and registry_name is the
  session module's own helper. In the CLI, activate_refusal applies to activate_session.
- boxes:: becomes sessions:: and switch::BoxGate becomes switch::SessionGate.
- The fallback watcher spawns with tokio::spawn under the same root span.
- OwnAddressReporter::registry comes without NET-129's dynamic-hold tracking. The attach
  path's holder lookup needs only the accessor.
- The line's DestroySessionRequest has no only_if_unfinalized, so the refusal tests assert
  the refused box is listed and not Active, destroy it plainly, and assert no record stays.

(cherry picked from commit 703bb7f)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

T100 — Activation refuses a declared port that something else holds

1 participant

0