Repository navigation
T27 — Configure or auto-select the proxy port and route two daemons' names at once - #1701
Conversation
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.
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
|
Babysitter review of T27 against the plan block in #1621, at Verdict: revise. The port work is sound: High High Medium
Low
The one out-of-layer file, |
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.
|
Round 1 of 10 on a conflict with the base branch, pushed as Resolved conflicts in:
Verify Outside the task's
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 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] |
|
/revise Round 1 only merged the base; the review at 0a728b8 (now be3eb7f) still stands. Fix, in this order:
Keep the existing test lines green ( |
…or-auto-select-the-proxy-port-and-rout
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.
…or-auto-select-the-proxy-port-and-rout
|
Round 2 of 10 on a /revise note, pushed as
Verify Outside the task's
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 |
|
/revise Round 2 (84bde88) closes four of the six items in the round-1 note with real tests: the
Files outside |
…or-auto-select-the-proxy-port-and-rout
…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.
|
Round 3 of 10 on a /revise note, pushed as
Verify Outside the task's
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] |
|
Babysitter note for round 4, not a new |
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.
…or-auto-select-the-proxy-port-and-rout
|
Round 4 of 10 on a failed check suite, pushed as
Verify Outside the task's
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] |
|
/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.
|
…or-auto-select-the-proxy-port-and-rout
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.
…or-auto-select-the-proxy-port-and-rout
|
Round 5 of 10 on a /revise note, pushed as
Verify Outside the task's
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 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] |
|
Approved: no findings at 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 — |
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.
|
Round 6 of 10 on the runtime's review findings, pushed as
Verify Outside the task's
Converted to a draft: something above needs a person before review. Review threads the coder judged:
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. |
There was a problem hiding this comment.
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.
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, andmin lsprints that port on aHOSTNAME 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 samewebin 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_idfield pluswith_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
cargo nextest run -p minimald proxy_listens_on_configured_portcargo nextest run -p minimald proxy_auto_selects_free_portcargo nextest run -p minimald two_daemons_route_hostnames_concurrentlycargo nextest run -p minimal min_prints_discovered_proxy_portVerify
min task run agent-verifypassed ([tasks.verify]).Out of scope
These files are outside the task's
layers:, so this PR is a draft:crates/minimald/src/net/mod.rsCloses #1621