8000 T27 — Configure or auto-select the proxy port and route two daemons' names at once by minimal-foundry[bot] · Pull Request #1701 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

T27 — Configure or auto-select the proxy port and route two daemons' names at once - #1701

Merged
norrietaylor merged 25 commits into
mainfrom
agent/1621-configure-or-auto-select-the-proxy-port-and-rout
Sep 25, 2026
Merged

norrietaylor merged 25 commits into
mainfrom
agent/1621-configure-or-auto-select-the-proxy-port-and-rout

Conversation

@minimal-foundry
Copy link
Copy Markdown
Contributor

T27 — Configure or auto-select the proxy port and route two daemons' names at once

slice: S6
kind: task
covers: NET-024, NET-025, NET-026, NET-027
size: L
est: 350 code, 280 tests
executor: agent
difficulty: needs care
model: strongest
diagnostics: the bundle's daemon log tail names at start the proxy port and whether it was configured or selected
observability: one info log line at daemon start with the proxy port and how it was chosen; min prints the port it discovered
layers:
- daemon:
- crates/minimald/src/main.rs: a proxy-port flag; the Config literal follows
- crates/minimald/src/server.rs: Config carries the port; start_host_proxies binds it, or port 0 to auto-select
- crates/minimald/src/test_harness.rs: its Config literal follows
- crates/minimald/src/net/proxy.rs: the fixed port constant becomes the default
- crates/minimald/src/rpc.rs: fills the port on the list and create responses
- crates/minimald/src/sessions.rs: the host id comes from the daemon instance so two daemons mint distinct names, and each daemon's loopback allocator draws from its own gvproxy's slice, so the two never collide
- crates/minimald/src/net/gvproxy_network.rs: registers names under the instance's host id
- crates/minimald-rpc/src/lib.rs: the list and create responses gain a defaulted proxy-port field
test: cargo nextest run -p minimald proxy_listens_on_configured_port
test: cargo nextest run -p minimald proxy_auto_selects_free_port
test: cargo nextest run -p minimald two_daemons_route_hostnames_concurrently
- cli:
- crates/minimal/src/cmd/list.rs: prints the discovered port
- crates/minimal/tests/cli.rs: the list-response literals follow; hosts the discovery test
test: cargo nextest run -p minimal min_prints_discovered_proxy_port
produces: none
consumes: none
proof: A daemon started with a configured port listens there, one started without picks a free port that min prints, and a native daemon and a VM daemon on one machine route both sets of names at the same time.

Proof

A daemon started with minimald --hostname-proxy-port <p> listens on exactly that port; started without one it binds port 0, keeps the port the OS chose, and min ls prints that port on a HOSTNAME PROXY: line, while the daemon log's startup line names the port and whether it was configured or selected; and two daemon instances on one machine now mint their box names under their own instance ids (the registry and the OwnIp DNS registration both take the daemon id as their host id) and land on their own auto-selected proxy ports, so both sets of names route at the same time — the two-daemons test registers the same web in both and routes both boxes concurrently through each daemon's own proxy.

Notes from the agent: Out-of-layer touch, one file: crates/minimald/src/net/mod.rs — SwitchClient gained a host_id field plus with_host_id/host_id(), so the daemon instance id travels to gvproxy_network.rs's DNS registration (and back out as the registry seed in sessions.rs) through the daemon-scoped switch; threading it as a parameter instead would have cascaded through provider.rs/exec.rs/session.rs/session_host.rs, four files outside the layers. It is named in the daemon commit's body.

Interpretation recorded: 'the fixed port constant becomes the default' is read as the constant being renamed DEFAULT_EGRESS_PROXY_PORT and re-documented as the port a deployment pins (what the published HTTP(S)_PROXY recipes assume); the new --hostname-proxy-port flag has no clap default, so an unconfigured daemon auto-selects. Giving the flag the 7654 default would keep the old behavior exactly where NET-025/NET-027 end it: a second daemon silently losing routing. A bind-failure fallback to auto-select was also rejected — the existing NET-021 retry-until-freed contract requires the daemon to keep trying the pinned port.

The sessions.rs note's second clause ('each daemon's loopback allocator draws from its own gvproxy's slic 8000 e') describes the per-instance allocation th…[truncated]

Tests

line exit tail
cargo nextest run -p minimald proxy_listens_on_configured_port 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-7d5b... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/9cdff/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/9cdff/tree/crates/ot) C
cargo nextest run -p minimald proxy_auto_selects_free_port 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-f188... Compiling stdlib v0.0.20 (/home/runner/.local/state/minimal/sessions/3229f/tree/crates/stdlib) Compiling version v0.6.0 (/home/runner/.local/state/minimal/sessions/3229f/tree/crate
cargo nextest run -p minimald two_daemons_route_hostnames_concurrently 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-a71a... Compiling stdlib v0.0.20 (/home/runner/.local/state/minimal/sessions/4e9e6/tree/crates/stdlib) Compiling version v0.6.0 (/home/runner/.local/state/minimal/sessions/4e9e6/tree/crate
cargo nextest run -p minimal min_prints_discovered_proxy_port 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-1924... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/61d8f/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/61d8f/tree/crates/ot) C

Verify

min task run agent-verify passed ([tasks.verify]).

Out of scope

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

  • crates/minimald/src/net/mod.rs

Closes #1621

minimald --hostname-proxy-port pins the port the host-side hostname
proxy listens on (NET-024); started without one, the daemon binds
port 0 and keeps the port the OS chose (NET-025). The fixed 7654
constant becomes DEFAULT_EGRESS_PROXY_PORT, the documented default a
deployment pins rather than a port the daemon demands.

The startup driver now reads the bound port off the listener — the
only place a selected one exists — publishes that port on the host
loopback in a microVM (it previously forwarded the pre-bind port,
which under port 0 forwarded nothing), and logs one line at start
naming the port and whether it was configured or selected, so a
diagnostics bundle's tail answers which port this daemon is on.
The port rides the ListSessions and CreateSession replies for
clients to discover (NET-026).

A daemon instance now mints its names under its own id: the switch
carries the daemon id as its host id, the sessions manager seeds
the hostname registry from it, and the OwnIp attach registers under
it — so two daemons on one machine register beside each other's
names and route both sets at the same time instead of the second
daemon overwriting the first's records (NET-027).

The in-VM Config literal stays on auto-select, so a VM daemon shares
its host with a native one without either losing routing.
`min ls` names the port the daemon's hostname proxy actually listens
on — the address an HTTP(S)_PROXY export needs, and the thing that
can no longer stay a constant now that a daemon without a pinned
port lands on an OS-chosen one (NET-026). The line prints in the
table mode a person reads; --raw stays bare ids for pipelines and
--json carries the field in the payload, so neither surface gains a
line. A daemon still bringing its proxy up, or one too old to carry
the field, prints nothing.

Driven end to end: the test runs the compiled binary against a
harness daemon whose startup auto-selected a port, and asserts the
line shows the port the reply carried and that the proxy accepts on
it.
The egress proxy is no longer on :7654 by default — it takes the
configured port or an OS-selected one — so the startup comment that
pinned it there now says what actually binds.
The new port round-trip case built its reply off the same base
struct the routing case had already moved from; clone so both
spread the intact base.
@coderabbitai
coderabbitai Bot commented Sep 25, 2026 •
Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

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

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 8cd3db0b-ad6d-45fe-8e65-9e06e4458073

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

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

@norrietaylor
Copy link
Copy Markdown
Member

Babysitter review of T27 against the plan block in #1621, at 0a728b8 (before the conflict round rebases it).

Verdict: revise. The port work is sound: --hostname-proxy-port pins the port, an unset port binds 0 and reads the chosen port back, the port is recorded only after the bind (and in a VM after the host publication), it rides the list and create replies with serde(default), one startup line says configured or selected, and min ls prints it. All four test lines exist, none self-skips, all exit 0. Three problems block it.

High crates/minimald/src/server.rs:226,254: the host id is now common::random_alphanumeric(5), regenerated on every daemon start, and it feeds both the proxy registry (sessions.rs:245) and the gvproxy DNS record (gvproxy_network.rs:147). NET-002 makes local the shipped zone's default host id and requires <name>.local.min.internal to route with a notice; min ls still tells users so (list.rs:335). After this PR that name neither routes nor resolves, and no test caught it because the NET-002 tests build the registry with local directly. The plan's own layer line asked for a per-instance host id, so this is a plan-versus-spec conflict that needs a ruling; the minimum that keeps NET-002 true is that local always routes as the canonical name regardless of the instance id.

High two_daemons_route_hostnames_concurrently: which backend answers is decided by the :{backend} port in the request, not by which daemon's proxy took it; the distinct-name checks only use the three-label form the finding above breaks; no VM daemon or host resolver path is started. It shows two auto-selected ports differ, not NET-027.

Medium

  • The zone answerer still binds fixed UDP 7656 (server.rs:868), so a second daemon's answerer never serves and the host resolver reaches one daemon's names only. NET-027 is met for the HTTP proxy alone.
  • The sessions.rs layer clause "each daemon's loopback allocator draws from its own gvproxy's slice" is not in the diff.
  • The flag has no default, and nothing that starts a daemon (min autospawn, minvmd) passes it, so every default install gets a new random port on each start and existing HTTP_PROXY=127.0.0.1:7654 exports go stale after every restart. The plan says the constant "becomes the default"; the PR only renamed it.

Low

  • In a VM the guest-chosen port is published on the same host port; if that is taken the publication retries forever instead of choosing another.
  • --hostname-proxy-port is absent from docs/reference/cli-minimald.md.
  • The configured-port test passes the port straight to start_host_proxies, skipping Server::run's Config path.

The one out-of-layer file, crates/minimald/src/net/mod.rs (with_host_id/host_id() on SwitchClient), is in scope for the plan's host-id intent. Round 1 on the base conflict is running; I post the revise when it lands.

Main's T5 (NET-109) removed the mTLS reverse proxy and its feature while
this branch made the hostname proxy's port configurable or OS-selected
(NET-024..NET-027). Both touched the proxy startup, the RPC replies, and
the reply literals.

Resolution: take main's removal of every mTLS surface (the field, the
state note, the CertAuthority, the feature, the Mtls startup arm) and
keep this branch's port plumbing on top of it — the hostname_proxy_port
fields on the list and create replies, the set/hostname_proxy_port
accessors, and the --hostname-proxy-port flag. The reply-literal tests
main added follow the new field, and this branch's discovery test sits
beside main's retired-surface scans.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on a conflict with the base branch, pushed as be3eb7f4b4a7.

Resolved conflicts in: crates/minimal/tests/cli.rs, crates/minimald-rpc/src/lib.rs, crates/minimald/src/main.rs, crates/minimald/src/net/proxy.rs, crates/minimald/src/rpc.rs, crates/minimald/src/server.rs

line exit tail
cargo nextest run -p minimald proxy_listens_on_configured_port 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-3a7c... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/a7b31/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/a7b31/tree/crates/ot) C
cargo nextest run -p minimald proxy_auto_selects_free_port 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-51af... Compiling stdlib v0.0.20 (/home/runner/.local/state/minimal/sessions/1d554/tree/crates/stdlib) Compiling version v0.6.0 (/home/runner/.local/state/minimal/sessions/1d554/tree/crate
cargo nextest run -p minimald two_daemons_route_hostnames_concurrently 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-1313... Compiling stdlib v0.0.20 (/home/runner/.local/state/minimal/sessions/657b0/tree/crates/stdlib) Compiling version v0.6.0 (/home/runner/.local/state/minimal/sessions/657b0/tree/crate
cargo nextest run -p minimal min_prints_discovered_proxy_port 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-de00... Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/e79dc/tree/crates/ot) Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/e79dc/tree/crates/args) C

Verify min task run agent-verify passed.

Outside the task's layers::

  • crates/minimald/src/net/mod.rs

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

At the merged head a daemon started with --hostname-proxy-port listens on exactly that port, one started without it picks a free port that min ls prints as the address its names route through, and two daemons on one machine — native and VM — mint distinct names under their own instance ids and route both sets at the same time, with every mTLS-proxy surface main retired gone from the tree.

Notes from the agent: Revise round 1's only item was the conflict with origin/main (T5, #1698, removed the mTLS reverse proxy while this branch made the hostname proxy's port configurable). Resolution, committed as the merge commit be3eb7f: took main's removal of every mTLS surface (the reply fields, the state note and accessors, the CertAuthority, the feature, the Mtls startup arm) and kept this branch's port plumbing on top of it — hostname_proxy_port on the list and create replies, the ServerState accessors, and the --hostname-proxy-port flag. Main's new reply-literal tests follow the new field, and this branch's NET-026 discovery test sits beside main's retired-surface scans. Two merge artifacts fixed beyond the markers: net/proxy.rs's module doc still linked the pre-rename constant EGRESS_PROXY_PORT (now DEFAULT_EGRESS_PROXY_PORT), and server.rs's record_serving kept a Self::Mtls arm plus doc sentence for the deleted variant (which main's retired_surfaces_absent source scan would also have flagged). The merge commit also carries origin/main's own incoming delta (AGENTS.md, Cargo.lock, Cargo.toml, crates/minimald/Cargo.toml, crates/minimald/src/connection.rs, crates/minimal/src/cmd/session.rs, justfile, scripts/build-initramfs.sh, scripts/kernel-bump-review.sh) — merge inputs from T5, not new work. Also note for the layer audit: crates/minimald/src/net/mod.rs (SwitchClient's host-id field and accessors, 22 lines) was added by this task's round-1 commit and is not named in the task's layers; it is where the instance host id the gvproxy_network.rs layer note requi…[truncated]

@norrietaylor
norrietaylor commented Sep 25, 2026 •
Copy link
Copy Markdown
Member

/revise Round 1 only merged the base; the review at 0a728b8 (now be3eb7f) still stands. Fix, in this order:

  1. Host id vs NET-002. Keep the per-instance host id from common::random_alphanumeric(5) for the instance-scoped names, but local must keep working as the canonical host id: <name>.local.min.internal routes through the proxy registry (crates/minimald/src/sessions.rs:245) and resolves in the gvproxy DNS record (crates/minimald/src/net/gvproxy_network.rs:147) on every daemon, exactly as NET-002 and the min ls line at crates/minimal/src/cmd/list.rs:335 promise. Register both ids (the instance id and local) for each session; add a test that builds the registry the way server.rs does (through the generated id, not by passing local directly) and proves the .local. name routes.

  2. Port default. DEFAULT_HOSTNAME_PROXY_PORT (7654) is the default when --hostname-proxy-port is unset: bind it first, and only if it is busy fall back to port 0 and report the selected port with the existing "selected" startup line. A configured non-zero port that is busy stays a hard failure. Cover the busy-default fallback with a test that holds 7654 (or a test-injected default) and asserts the daemon comes up on another port and reports it.

  3. Zone answerer. The answerer at crates/minimald/src/server.rs:868 still binds fixed UDP 7656; give it the same configured-or-selected treatment (flag --zone-answerer-port, default 7656, fall back to 0 when busy, selected port reported alongside the proxy port in the startup line and ListSessionsResponse), otherwise a second daemon's names never resolve on the host.

  4. Allocator slice. The sessions.rs layer clause "each daemon's loopback allocator draws from its own gvproxy's slice" is not in the diff: derive the allocator's range from this daemon's switch subnet rather than a shared constant, with a unit test that two daemons with different subnets get disjoint ranges.

  5. Strengthen two_daemons_route_hostnames_concurrently: pick the backend by the Host header only (no :{backend} port in the request), assert through both daemons' proxies that <name>.local.min.internal and the instance-id form each reach the right backend, and assert the two daemons' registries do not see each other's names.

  6. Low: in a VM, when the host publication of the guest-chosen port is refused, choose another port instead of retrying forever; add --hostname-proxy-port and --zone-answerer-port to docs/reference/cli-minimald.md; route the configured-port test through Server::run's Config path instead of calling start_host_proxies directly.

Keep the existing test lines green (cargo nextest run -p minimald proxy_listens_on_configured_port, proxy_auto_selects_free_port, two_daemons_route_hostnames_concurrently, -p minimal min_prints_discovered_proxy_port).

The instance host id this daemon's registry and gvproxy DNS records
moved to (NET-027) displaced the one label every daemon used to answer:
with it, <name>.local.min.internal — the pre-instance form NET-002
promised — stopped resolving and routing.

Both sides now answer the pair: the routing registry carries the
instance id plus local, so the deprecated three-label fallback maps
either; and the gvproxy registration publishes the name under both
host labels beside the two-label form. A local name keeps working on
every daemon, while a second daemon still registers beside the first
under its own id.
The reserved local range published box addresses come from is
host-global, so the slice a daemon draws from must follow its own
gvproxy's switch subnet — never a constant two daemons would both
start at. LoopbackAllocator keeps the per-daemon half of that
allocation: one /27 indexed by the subnet's third octet, handed out
without reuse. Two daemons on different subnets draw disjoint ranges,
which is the address half of routing both sets of names at the same
time (NET-027); the host-global arbitration and the publish path that
spends these addresses are NET-010's, and the manager holds the
allocator for them.
The hostname proxy and the box-zone answerer now take the same port
policy: the flag pins, and an unset one tries the documented default
(7654 / 7656) first, asking the OS for a free port only when it is
busy. The port it ended up on and who chose it — configured, default,
or selected — ride the startup line and the list and create replies,
so a client can name the port to point HTTP(S)_PROXY and the host
resolver at. A pinned port that is busy stays a hard failure with its
retry and report; a guest-chosen port the host refuses to publish is
released and a fresh one picked rather than retried forever.

--zone-answerer-port joins --hostname-proxy-port, both documented in
the daemon reference. The configured-port proof now drives Server::run
through the Config the flags build; and the two-daemon proof routes
the same Host header through both daemons' proxies — two-label,
instance, and local forms — with each daemon's own box answering
through its own proxy, the other's instance form refused, and neither
registry seeing the other's names.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 2 of 10 on a /revise note, pushed as 84bde88ea76d.

line exit tail
cargo nextest run -p minimald proxy_listens_on_configured_port 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-0936... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/6e893/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/6e893/tree/crates/ot) C
cargo nextest run -p minimald proxy_auto_selects_free_port 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-9b04... Compiling stdlib v0.0.20 (/home/runner/.local/state/minimal/sessions/aceea/tree/crates/stdlib) Compiling version v0.6.0 (/home/runner/.local/state/minimal/sessions/aceea/tree/crate
cargo nextest run -p minimald two_daemons_route_hostnames_concurrently 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-0f27... Compiling stdlib v0.0.20 (/home/runner/.local/state/minimal/sessions/66fe0/tree/crates/stdlib) Compiling version v0.6.0 (/home/runner/.local/state/minimal/sessions/66fe0/tree/crate
cargo nextest run -p minimal min_prints_discovered_proxy_port 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-32b3... Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/0ccb7/tree/crates/ot) Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/0ccb7/tree/crates/args) C

Verify min task run agent-verify passed.

Outside the task's layers::

  • crates/minimald/src/net/dns.rs
  • crates/minimald/src/net/mod.rs
  • docs/reference/cli-minimald.md

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

A daemon pinned to a port listens on exactly it — and so does its box-zone answerer, with both ports carried on the list and create replies and printed by min ls; a daemon started with no port takes the documented default (7654/7656) when free and relocates to a free port, reported as selected, when busy; and two daemons on one machine route both sets of names at the same time — each answering its own instance's zone, the shared local zone NET-002 promised, and the two-label form, through its own proxy, with each daemon's loopback allocator drawing from its own gvproxy's slice.

Notes from the agent: Round 2 (directive present, no review threads listed — none to judge). Out-of-layers touches: crates/minimald/src/net/dns.rs — the directive's item 1 (keep local routing beside the instance host id) is decided in the registry's host-id list, which lives there; the change is host_ids_for() plus the registry answering [instance, local]. docs/reference/cli-minimald.md — item 6 explicitly asked for the two flags' rows. crates/minimald/src/net/mod.rs was NOT touched this round; it carries the round-1 SwitchClient host-id work already on the branch. Two judgement calls worth review's eye: (1) the zone_answerer_port field was added to CreateSessionResponse as well as ListSessionsResponse — the reviewer named only the list, but the create reply already carries the same discovery pair (unavailable + proxy port) and the wire is defaulted/skipped either way; the same is true of the answerer's refused-publish re-pick, which mirrors the proxy's. (2) the LoopbackAllocator is prospective: it is constructed by Manager::init from the daemon's switch subnet and unit-tested for disjointness, but no publish path is wired to it yet (NET-010/NET-129), so it carries #[allow(dead_code)] with a comment, per the existing maintenance() precedent in server.rs. The two-daemons test shapes its two daemons as in_microvm hosts so each box publishes on its own loopback address (127.0.0.1/…[truncated]

@norrietaylor
Copy link
Copy Markdown
Member

/revise Round 2 (84bde88) closes four of the six items in the round-1 note with real tests: the local zone beside the instance id, default 7654 with fallback only when busy, the answerer's own default port, and the two-daemon test. Two remain open, plus two new mediums.

  1. Item 4, the allocator slice, is not wired. LoopbackAllocator::for_switch_subnet (crates/minimald/src/sessions.rs:351) is built at init (sessions.rs:265) but every switch is constructed with SwitchSubnet::default() (crates/minimald/src/net/mod.rs:227, crates/minimald/src/server.rs:246), so two real daemons draw the same slice, and the field is #[allow(dead_code)] with no publish caller. Give each daemon its own switch subnet at construction (derive it from the instance id or configure it), pass it through SwitchClient::with_subnet, and add a test that two daemons started through Server::run get disjoint slices. Drop the dead_code allowances by wiring the allocator into the publish path or, if that is out of this task's scope, say so in the doc comment and keep the field out until it has a caller.
  2. Medium: a VM daemon can lose 7654 at boot. expose_proxy_on_host (server.rs:1536) returns a failure for every cause, including an unreachable shuttle, gvproxy not up yet, and the one-second timeout, and the default-then-select policy then moves both listeners to random ports (server.rs:1271-1290) until the daemon restarts. Re-pick only when the host port is actually taken; on transport errors and timeouts keep retrying the same port with the existing backoff. A test that a transient publish failure keeps the default port.
  3. Medium: answerer_relocates_when_its_default_port_is_busy can flake. server.rs:2636 unwraps a bind on the real 7656; the six Server::run tests using test_config plus proxy_auto_selects_free_port now bind the real defaults too, so under nextest's parallel processes or beside a running minimald the unwrap panics. Use .ok() and skip when held, as the 7654 test does.
  4. Low: any bind error triggers the busy fallback (server.rs:1237); match on AddrInUse only.
  5. Low: the configured-port test lost its "a name routes with 200" assertion; restore it.
  6. Low: min ls prints only the proxy port; print the answerer port beside it, and merge the answerer's startup line into the proxy's or note in docs/reference/cli-minimald.md that two lines are logged.

Files outside layers: (crates/minimald/src/net/dns.rs, docs/reference/cli-minimald.md) are in scope for this note; the PR stays a draft until the round lands and is then un-drafted.

…acks

Two things a host running two daemons needs, from one start path:

Each daemon's gvproxy switch now takes its own /24 inside the default
switch /16 - configured by the new `switch_subnet_octet` Config field, or
derived from the instance id when none is pinned - instead of every
daemon defaulting to the same subnet. `ServerState::new` reads the octet
through `SwitchClient::with_subnet` and logs one start line naming the
subnet and the slice of the reserved local range it indexes, so a reader
of two daemons' logs can see the two hold disjoint halves (NET-027).
The sessions manager no longer builds an allocator nobody reads; the
start line is the reader, and the publish path that will spend these
addresses (NET-129/NET-010) is another task's.

Port relocation now happens only when the port is genuinely taken. A
host publish that fails transiently - the shuttle not up at boot, a
stalled exchange - keeps the port it bound (the default included) and
retries with the existing backoff, so a VM daemon that starts before its
host gvproxy no longer lands on a random port; only a forwarder that
answered and refused re-picks. The busy-bind fallback likewise fires
only on EADDRINUSE (`BindFailure::is_addr_in_use`), not on every bind
error.

Tests: two daemons through `Server::run` announce disjoint slices and a
third derives its own; a transient publish keeps the default; the
configured-port test holds the daemon's state while `Server::serve`
runs the real accept loop, so a live name routes 200 beside the ghost's
502; the answerer's busy-default test binds with `.ok()` so a parallel
holder is not a panic.
`min ls` showed only the hostname proxy's port, but the box-zone
answerer's UDP port is the other half of the same discovery: it is what
a host resolver is pointed at for `*.min.internal`, it auto-selects the
same way the proxy does, and a machine running two daemons has two of
them. Print a ZONE ANSWERER line beside the HOSTNAME PROXY line, and
drive both listeners in the CLI test so both printed lines are asserted
against the compiled binary.
The daemon logs one serving line per listener at startup - the hostname
proxy (component=dns-proxy) and the box-zone answerer
(component=zone-answerer) - each naming its port and how it was chosen
(configured, default, or selected). Two lines, not one merged one: each
listener retries and comes up on its own schedule. Name the pair in the
run reference so a reader of the log knows both are expected.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

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

line exit tail
cargo nextest run -p minimald proxy_listens_on_configured_port 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-34ea... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/60693/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/60693/tree/crates/ot) C
cargo nextest run -p minimald proxy_auto_selects_free_port 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-375d... Compiling stdlib v0.0.20 (/home/runner/.local/state/minimal/sessions/924e6/tree/crates/stdlib) Compiling version v0.6.0 (/home/runner/.local/state/minimal/sessions/924e6/tree/crate
cargo nextest run -p minimald two_daemons_route_hostnames_concurrently 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-62a5... Compiling stdlib v0.0.20 (/home/runner/.local/state/minimal/sessions/01bd1/tree/crates/stdlib) Compiling version v0.6.0 (/home/runner/.local/state/minimal/sessions/01bd1/tree/crate
cargo nextest run -p minimal min_prints_discovered_proxy_port 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-2426... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/8d58c/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/8d58c/tree/crates/ot) C

Verify min task run agent-verify passed.

Outside the task's layers::

  • crates/minimald/src/net/dns.rs
  • crates/minimald/src/net/mod.rs
  • docs/reference/cli-minimald.md

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

A daemon started with a configured port listens there, one started without picks a free port that min prints — both listener ports now, the proxy's and the answerer's — and a native daemon and a VM daemon on one machine route both sets of names at the same time: each takes its own /24 in the host's switch space (configured or derived from its instance id) and announces on its start line the disjoint slice of the reserved local range it draws from, so a reader of two daemons' logs can see the two never collide.

Notes from the agent: Round-3 items, all six addressed. (1) Each daemon's SwitchClient is built with SwitchClient::with_subnet on a /24 inside the default 100.64/16 — pinned by a new serde-defaulted Config field switch_subnet_octet (no CLI flag; it is the seam tests and deployments pin, and the derivation from the instance id is the default path), or derived FNV-1a from the instance id; ServerState::new logs daemon_id, subnet, and loopback_slice on one start line. The sessions Manager no longer holds an unused allocator: for_switch_subnet/range gained their real caller (the start line), and allocate alone keeps a targeted #[allow(dead_code)] naming its future caller (NET-129/NET-010, the publish path — another task's). (2) expose_proxy_on_host returns HostPublishFailure{report, port_taken}; only a forwarder that answered and refused re-picks, so a VM daemon that boots before its host gvproxy keeps the default and retries — tested through the real shuttle path in a_transient_publish_failure_keeps_the_default_port, and a test-only HostExpose::Fixed makes the refused case deterministic. (3) The answerer busy test binds 7656 with .ok(). (4) Both drivers relocate only on BindFailure::is_addr_in_use; it matches the rendered OS error rather than a typed kind because a kind field would have meant editing net/answerer.rs, which the layers do not name. (5) Restoring the configured-port test's 200 assertion required splitting Server::run into run plus a priv…[truncated]

@norrietaylor
Copy link
Copy Markdown
Member

Babysitter note for round 4, not a new /revise: the failed suite at c0990aa is the macOS hypervisor e2e, and the failure is guest DNS. Cold activate and session exec pass, then every guest egress probe fails with curl exit 6, "Could not resolve host", for both example.com and example.org. The same lane was green at 84bde88, and the only functional commit between is 4186f63 (each daemon its own switch /24). A per-daemon subnet that gvproxy's rendered config, the guest resolver address, or the daemon-IP used for the host publish do not all agree on would produce exactly this. Verify the three still derive from one subnet on the VM path.

The per-daemon /24 a daemon's id derives was being handed to every
daemon's SwitchClient, including one in a microVM — where the switch is
not the daemon's. A guest daemon attaches its boxes' taps to the host
gvproxy minvmd owns, whose config the guest can neither read nor change:
minvmd renders it with the default 100.64/16. Every address the guest
derives from its switch must be one that switch answers at, and a
derived /24's gateway - 100.64.<octet>.1 - is an address no gvproxy
answers: a host-address box's resolver pointed there (HostNet in a VM
replaces the rootfs's resolver with the switch's DNS server), an own-IP
box's tap carried it as its gateway, and every box in the VM lost
egress, DNS first - the macOS e2e's guest-egress proof failed with curl
status 6 against both hosts on this branch while the same lanes were
green one head earlier, on main, and on the native lane.

The subnet now follows the switch's owner
10000
: a native daemon that spawns
its own gvproxy from a config it renders keeps its own /24 - configured
when a deployment pins the octet, else derived from the instance id
(NET-027) - and a microVM daemon carries the host gvproxy's default
/16, whatever its config pins, because it cannot move a switch it does
not own onto the octet. The /16 also restores the legacy host literal
the relay watches to NET-004's own 100.64.255.254 (the /16's broadcast
minus one) instead of the derived /24's.

Tests: a microVM daemon through ServerStateHandle::new - with an octet
pinned, to show the pin cannot move a switch the guest does not own -
logs the host gvproxy's /16 on its start line, the native arms keep the
pin and the derivation, and the host switch's DNS server is the /16's
own gateway. The change restores exactly the pre-branch behavior for a
microVM daemon (the /16 its root tap is configured with, in
guest::bring_up_root_egress), so the native lanes are untouched.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 4 of 10 on a failed check suite, pushed as e9c4852b4ebb.

line exit tail
cargo nextest run -p minimald proxy_listens_on_configured_port 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-39ff... Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/e6f86/tree/crates/ot) Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/e6f86/tree/crates/args) C
cargo nextest run -p minimald proxy_auto_selects_free_port 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-a8db... Compiling stdlib v0.0.20 (/home/runner/.local/state/minimal/sessions/2d0d7/tree/crates/stdlib) Compiling version v0.6.0 (/home/runner/.local/state/minimal/sessions/2d0d7/tree/crate
cargo nextest run -p minimald two_daemons_route_hostnames_concurrently 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-fdcb... Compiling stdlib v0.0.20 (/home/runner/.local/state/minimal/sessions/6af25/tree/crates/stdlib) Compiling version v0.6.0 (/home/runner/.local/state/minimal/sessions/6af25/tree/crate
cargo nextest run -p minimal min_prints_discovered_proxy_port 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-3add... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/00d5a/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/00d5a/tree/crates/ot) C

Verify min task run agent-verify passed.

Outside the task's layers::

  • crates/minimald/src/net/dns.rs
  • crates/minimald/src/net/mod.rs
  • docs/reference/cli-minimald.md

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

Both failed checks were one failure: the VM session e2e's guest-egress proof died resolving DNS, on both VM lanes. A microVM daemon now carries the host gvproxy's own 100.64/16 on its switch client — the native daemon keeps the per-daemon /24 its id derives — so a box in the VM resolves through the switch's real gateway again, and the native-daemon-and-VM-daemon pair the task's proof names keeps egress and hostname routing on both sides at the same time instead of the VM side's boxes silently losing DNS, NAT and every route.

Notes from the agent: Diagnosis: the directive's two failed checks reduce to one — ci-macos-success is the lane aggregator and failed only because the macOS e2e job did (step 'Session e2e via the CLI'), whose guest-egress proof failed with curl status 6 (couldn't resolve host) against both hosts; the Linux KVM lane's run 36132500210 (job 108068185547) failed the identical step with the identical annotation at the same head, checked via the public API. Both VM lanes were green one head earlier (84bde88) and main is green, isolating round 3's 4186f63: it handed every daemon's SwitchClient a /24 derived from its instance id, including a microVM daemon's — but a guest daemon does not own its switch; it attaches to the host gvproxy minvmd owns, which renders the default /16. The guest derives every switch address from that subnet (a HostNet box's resolver is the switch's DNS server; an OwnIp box's tap carries the subnet's lease, gateway and netmask; the legacy-literal relay watches the subnet's broadcast-minus-one), and the derived /24's gateway 100.64..1 is an address no gvproxy answers, so every box in the VM lost egress, DNS first. Fix: switch_subnet_for in server.rs — the subnet follows the switch's owner (native: derived or pinned /24, NET-027 intact; microVM: the host gvproxy's /16 whatever the config pins, because the guest cannot move a switch it does not own), restoring byte-identical pre-branch behavior for the VM daem…[truncated]

@norrietaylor
Copy link
Copy Markdown
Member

/revise Rounds 3 and 4 (4186f63, a4676e3, c0990aa, 14b9532; head e9c4852) close items 2, 3, 5 and 6 of the round-2 note with real tests, and the VM lanes are green again. Two items block, one is optional.

  1. Blocking, invisible to every hosted lane: the busy-port fallback never fires in a microVM. BindFailure::is_addr_in_use (crates/minimald/src/net/proxy.rs:196-199) matches the rendered text "Address already in use", which is glibc's. The guest daemon is a musl build (scripts/build-initramfs.sh, *-linux-musl), and musl renders EADDRINUSE as "Address in use", so in a VM the predicate is always false and a daemon whose 7654 or 7656 is busy loops "retrying with backoff" forever with the proxy unavailable, where before round 3 it relocated. The doc comment saying the text is the same on every libc is wrong. Carry the io::ErrorKind on BindFailure (both bind sites hold the io::Error: proxy.rs:235, answerer.rs:393) and compare against ErrorKind::AddrInUse. Add the negative test: a bind failure that is not AddrInUse must not relocate. The existing relocation tests under just test-cross cover the musl half.

  2. Blocking: item 1 is hollow for VM daemons. Round 4's switch_subnet_for (server.rs:364-372) returns DEFAULT_SUBNET whenever in_microvm, which correctly restores the guest's switch addresses (gvproxy's config, the guest resolver, and the publish daemon-IP all assume it), but the loopback slice is keyed to the switch's third octet, so every microVM daemon now announces the same 127.64.0.0 slice. Two VMs on one mac host is the primary NET-027 case, and the existing test at server.rs:2810 uses in_microvm: true. Index the slice by the daemon id (octet_for_daemon_id, or the pinned octet when given) independently of the switch subnet, and add an in_microvm: true pair to two_daemons_draw_disjoint_loopback_slices. If the slice is deliberately native-only for this task, say so in the allocator doc and the start line, and open the follow-up.

  3. Optional: the "returned HTTP" classification in expose_proxy_on_host (server.rs:1769) has no test and treats any non-2xx as "port taken"; one test each for the taken and transient arms of the classifier.

crates/minimald/src/main.rs (the switch_subnet_octet: None literal) is in scope. Keep the four plan test lines green. The PR stays a draft until the round lands.

BindFailure::is_addr_in_use matched the rendered OS error text, which is
the libc's: glibc spells EADDRINUSE "Address already in use", musl —
the *-linux-musl build every guest daemon is — "Address in use". Inside
a microVM the predicate was therefore always false and a guest daemon
whose 7654 or 7656 was busy looped "retrying with backoff" forever with
the proxy unavailable instead of relocating (NET-025).

Carry io::ErrorKind beside the rendered text, from both bind sites that
hold the io::Error, and compare against ErrorKind::AddrInUse: the kind is
the one fact both libcs agree on. The driver keeps its rule that only a
busy address relocates (NET-021), now proved by a test that drives a bind
failure the host cannot assign — EADDRNOTAVAIL under glibc and musl
alike — and asserts it keeps its address; the predicate's independence
from the text is asserted directly against both libcs' renderings.
The slice of the reserved local range a daemon's published boxes come
from was indexed by the third octet of its switch subnet, which a
microVM daemon cannot choose: its switch is the host gvproxy's, on the
default /16, so every VM on one machine announced the same 127.64.0.0
slice — the primary NET-027 case is two VMs on one host.

Index the slice on the daemon's own octet — pinned when a deployment
pins one, else derived from its instance id — independently of the
switch subnet: on a native host the octet is still its own /24's third
octet, so leases and slice move together as before, while two VM
daemons take two distinct octets and never meet on one slice. The
start line now names the octet beside the slice it indexes, and
two_daemons_draw_disjoint_loopback_slices grows an in_microvm pair
that holds the same two slices as their native twins on the same
octets while their switch stays on the shared /16.
expose_proxy_on_host decides whether a failed host-loopback publish
means the host port was taken (the one failure a guest-chosen port
re-picks from) by looking for the HTTP status in the io::Error post_json
returns — a classification no test rea
10BC8
ched, on the arm a VM daemon's
port policy turns on.

Split the publish from the channel it rides (the vsock shuttle from a
microVM, a unix control socket otherwise) and drive each arm of the
classifier over a unix stand-in: a forwarder that answers and refuses
reads as taken and names the status it answered with, and one that never
answers — nothing listening where the channel points, the shape a daemon
that starts before its host gvproxy sees — reads as transient, keeping
the port it bound.
No code-meaning change: three spots the last commits added or moved
landed off the tree's rustfmt rules.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 5 of 10 on a /revise note, pushed as 2d1fb260b144.

line exit tail
cargo nextest run -p minimald proxy_listens_on_configured_port 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-2fe7... Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/ab269/tree/crates/ot) Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/ab269/tree/crates/args) C
cargo nextest run -p minimald proxy_auto_selects_free_port 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-b9a8... Compiling stdlib v0.0.20 (/home/runner/.local/state/minimal/sessions/18bbb/tree/crates/stdlib) Compiling version v0.6.0 (/home/runner/.local/state/minimal/sessions/18bbb/tree/crate
cargo nextest run -p minimald two_daemons_route_hostnames_concurrently 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-7752... Compiling stdlib v0.0.20 (/home/runner/.local/state/minimal/sessions/a96f4/tree/crates/stdlib) Compiling version v0.6.0 (/home/runner/.local/state/minimal/sessions/a96f4/tree/crate
cargo nextest run -p minimal min_prints_discovered_proxy_port 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-6737... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/314ed/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/314ed/tree/crates/ot) C

Verify min task run agent-verify passed.

Outside the task's layers::

  • crates/minimald/src/net/answerer.rs
  • crates/minimald/src/net/dns.rs
  • crates/minimald/src/net/mod.rs
  • docs/reference/cli-minimald.md

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

A daemon started with a configured port listens there, one started without picks a free port that min prints, and two daemons on one machine — a native daemon and a VM daemon, or two VM daemons — route both sets of box hostnames at the same time, each drawing its own 127.64.0.0/24 slice keyed to its own slice octet rather than to the shared default /16 switch; and the busy-port fallback now actually fires inside a microVM, because the predicate matches the OS error's ErrorKind::AddrInUse instead of the libc-rendered text that glibc and the musl guest build spell differently.

Notes from the agent: Revise round 5 (directive note, three items — all three done). Item 1: BindFailure carries the io::ErrorKind beside the rendered reason and is_addr_in_use compares ErrorKind::AddrInUse, never the text (proxy.rs); negative coverage in two places — busy_port_predicate_matches_the_error_kind_not_the_text (proxy.rs; asserts both libcs' renderings match by kind, a text-matching but AddrNotAvailable-kind report does not, and a live held-port bind reports the right kind) and a_non_busy_bind_failure_keeps_its_address (server.rs; an EADDRNOTAVAIL bind on 192.0.2.1 never relocates). crates/minimald/src/net/answerer.rs is outside the task's layers: directive item 1 names it as the second BindFailure construction site (the answerer's UDP bind) and it needed the same kind: error.kind() field; noted here as the one out-of-scope file. Item 2: LoopbackAllocator::for_switch_subnet became for_slice_octet, so a slice is keyed by the daemon's own octet (pinned, else octet_for_daemon_id) and a microVM daemon — whose switch is the shared default /16 — no longer collapses onto for_slice_octet(0)'s slice; ServerState::new logs the octet and slice at daemon start, and two_daemons_draw_disjoint_loopback_slices gained the in_microvm: true pair (octets 37/52: same 100.64.0.0/16 switch line, disjoint slices, VM slice equal to its own octet's). main.rs's switch_subnet_octet: None literal …[truncated]

@norrietaylor
norrietaylor marked this pull request as ready for review September 25, 2026 14:18
@norrietaylor
norrietaylor requested a review from a team as a code owner September 25, 2026 14:18
@minimal-foundry
minimal-foundry Bot commented Sep 25, 2026 •
Copy link
Copy Markdown
Contributor Author

Approved: no findings at b991bfa5444b.

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 — git log on the touched files shows only this PR's commits and a merge from main; the EGRESS_PROXY_PORT→DEFAULT_EGRESS_PROXY_PORT rename has no stale references.

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

Comment thread crates/minimald/src/server.rs Resolved
Comment thread crates/minimald/src/net/gvproxy_network.rs 8 Resolved 10BC8
The Config field, the switch-subnet doc, the daemon-start comment, and
the LoopbackAllocator's own docs said two daemons on one machine never
draw from overlapping publish slices — but the slice index is
octet % LOOPBACK_SLICES, so two ids that derive octets congruent mod
the slice count (about one pair in eight) index one shared slice.

Say what actually holds everywhere a claim was made: distinct octets
always give distinct switch /24s (leases never collide); the slices are
disjoint only between incongruent octets, with the wrap visible in two
daemons' start lines (the same loopback_slice range) and arbitrated
host-globally by NET-010's allocation. Fix the garbled parenthetical on
for_slice_octet ('the last eighth of the address space') to state the
wrap it meant, and make LOOPBACK_SLICES pub(crate) so the tests can
name the predicate they assert.

The two-daemon test now covers the wrap's both arms with ids chosen for
their derived octets (congruent pair shares a slice, incongruent pair
does not), and the deriving daemon's relation to the pinned pair
follows the same predicate instead of asserting only its own
derivation.
The two-daemon routing test proved NET-027 only at the per-daemon
registry level; the DNS publish path it is about was never exercised.
Add a test that drives register_dns_name the way finish_own_ip_attach
does — host_ids_for per daemon: its own id, then local — for two
daemons each holding a box named web, against one fake gvproxy control
endpoint standing in for the host gvproxy two VM daemons share.

What it pins at the wire: the instance-scoped records are distinct
names, so both daemons' boxes resolve beside each other; the shared
two-label web row and the deprecated web.local row are the same names
published twice at different leases, which gvproxy's newest-wins merge
resolves last-writer-wins — the ambiguity across daemons NET-010's
host-global allocation arbitrates. The attach-path comment now draws
the same line instead of claiming the second daemon registers beside
the first unconditionally: only its instance-scoped record does.
@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.

@minimal-foundry minimal-foundry Bot added the review:approved minimal-review approved the current head label Sep 25, 2026
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 6 of 10 on the runtime's review findings, pushed as b991bfa5444b.

line exit tail
cargo nextest run -p minimald proxy_listens_on_configured_port 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-ba08... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/87bbb/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/87bbb/tree/crates/ot) C
cargo nextest run -p minimald proxy_auto_selects_free_port 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-56b9... Compiling stdlib v0.0.20 (/home/runner/.local/state/minimal/sessions/f8dd3/tree/crates/stdlib) Compiling version v0.6.0 (/home/runner/.local/state/minimal/sessions/f8dd3/tree/crate
cargo nextest run -p minimald two_daemons_route_hostnames_concurrently 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-b2b2... Compiling stdlib v0.0.20 (/home/runner/.local/state/minimal/sessions/a6382/tree/crates/stdlib) Compiling version v0.6.0 (/home/runner/.local/state/minimal/sessions/a6382/tree/crate
cargo nextest run -p minimal min_prints_discovered_proxy_port 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-db61... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/32092/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/32092/tree/crates/ot) C

Verify min task run agent-verify passed.

Outside the task's layers::

  • crates/minimald/src/net/answerer.rs
  • crates/minimald/src/net/dns.rs
  • crates/minimald/src/net/mod.rs
  • docs/reference/cli-minimald.md

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

Review threads the coder judged:

  • 👍 valid — PRRT_kwDOPdfcxM6mCcjz: The 'never overlapping publish slices' claim did not hold for derived octets. Weakened every place it stood (Config field, switch_subnet_for, the start-line comment, the allocator docs — including fixing the garbled 'last eighth of the address space' parenthetical) to state the mod-LOOPBACK_SLICES w (not posted on the thread: not an open thread on the pull request)
  • 👍 valid — PRRT_kwDOPdfcxM6mCcj6: The two-daemon proof never exercised the DNS publish path. Added instance_zone_records_stay_distinct_in_one_shared_zone, driving register_dns_name the way finish_own_ip_attach does (host_ids_for per daemon) for two daemons each holding a box named web into one fake gvproxy control endpoint: the inst (not posted on the thread: not an open thread on the pull request)

A daemon started with a configured port listens there, one started without picks a free port that min prints, and a native daemon and a VM daemon on one machine route both sets of names at the same time — with both boundaries the review named now said out loud: the instance-scoped ..min.internal forms are the records two daemons can rely on (pinned by a test that drives both daemons' /services/dns/add publishes into one shared gvproxy zone), while the shared two-label and deprecated .local forms are the same row published twice, resolved last-writer-wins by gvproxy's merge until NET-010's host-global allocation arbitrates; and two daemons' publish slices are disjoint only while their slice octets stay incongruent modulo the reserved range's slice count (about one pair in eight lands on one shared slice, visible as the same loopback_slice range on both start lines), not unconditionally.

Notes from the agent: Review round 6, two threads, both judged valid and fixed; no behavior change this round (docs, comments, tests, and one pub(crate) const). The two remaining shared-name/slice ambiguities are NET-010's host-global allocation by design — the spec says NET-027's addresses come from it — so this round makes them observable and documented rather than fixing them, which the finding offered as acceptable. The loopback allocator itself is still dead code pending NET-129's publish path. The cli layer (crates/minimal/src/cmd/list.rs, crates/minimal/tests/cli.rs) needed no change this round; both exist at head and its test line passes. All three files touched are daemon-layer files named in the task's layers.

@minimal-foundry
minimal-foundry Bot marked this pull request as draft September 25, 2026 15:32
@norrietaylor
norrietaylor marked this pull request as ready for review September 25, 2026 15:37
@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.

Rounds 5 and 6 verified against the plan block for T27: the busy-port predicate matches the io::ErrorKind with both libc texts and a negative case covered; the loopback slice is keyed on the daemon's own octet independent of the switch subnet, with an in-VM pair in the disjoint-slice test and the eight-slice wrap-around stated honestly at the claim, the allocator, and the start line; the shared-zone publish is driven end to end with the instance-scoped records shown distinct and the two-label ambiguity acknowledged until NET-010; classifier tests cover both arms. minimal-review approves the head, no open threads, four plan test lines green.

@norrietaylor
norrietaylor enabled auto-merge (squash) September 25, 2026 15:37
@norrietaylor
norrietaylor merged commit fecbfd4 into main Sep 25, 2026
33 checks passed
@norrietaylor
norrietaylor deleted the agent/1621-configure-or-auto-select-the-proxy-port-and-rout branch September 25, 2026 15:41
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.

T27 — Configure or auto-select the proxy port and route two daemons' names at once

1 participant

0