Repository navigation
T66 — Register a box on the host and hand it its addresses - #1789
norrietaylor merged 22 commits into
Conversation
The host-side box table (NET-138) had no way to be filled from a creator: the activating client had no door to minvmd, so an own-address box's row never landed in the registry the egress gate decides by, and its addresses were left to the daemon to draw itself. Add the VM host daemon's box-registration control socket, beside the ssh socket in the provider-instance dir, speaking one JSON line each way over the wire types in minimald-rpc (RegisterBoxRequest / RegisterBoxReply). A registration allocates the box's switch address from the plan's lease run and its loopback address from the slice the host switch publishes at — shared cursors across registry clones, so no address is ever handed twice — fills the row from the client's expanded spec, and answers with the addresses; one info line per registration names the box and both of them. run.rs binds the socket at boot and degrades to a warn, not a failed boot, when it cannot.
An own-address activation on a minvmd-backed host had no way to put its box into the host-side table the egress gate decides by: it created the session without telling the VM host daemon anything, so the box drew its own switch address and its declared rules reached no host-side gate. Before the create, activate registers the box over the VM host daemon's control socket — the box's name, its expanded ingress ports, its egress policy — and carries the addresses the daemon hands back in the create request, so the in-VM daemon attaches with the handed switch address. A host-ip box shares the node's own row, a none box has no switch address, and a native daemon has no box table: all three register nothing. A registration that cannot be made degrades to a warning, never a failed activation, the same way a supervisor predating the socket behaves. Carries the wire groundwork with it: sessions::BoxAddresses (the pair handed together) rides the SessionConfig and the session Record, and the struct literals across the workspace gain the new field.
An own-address session now attaches at the switch and loopback addresses the VM host daemon allocated for its box registration and handed back in the create request, instead of drawing one from the switch allocator itself. The handed address still lands in the lease table, so the never-reuse rule and every downstream consumer of that table see it the same way as a self-allocated one. - Record carries the handed pair from the create request into the session launcher, and the task path passes none deliberately: a task sandbox is not the box the registration named. - SwitchClient::attach_handed validates the handed address against the plan's PTask run and records the lease without a draw; a handed address outside the plan is NetError::HandedAddressOutsidePlan. - The one debug line per attach names the handed address and whether it was honored from the host. The switch allocator's next draw skips past a handed lease, so an interim self-allocating session cannot take the box's address.
cargo fmt across the three box-registration commits; no code-meaning change.
- read_request_line carries the slice invariant as an expect with a reason, matching the header-parts precedent. - the test register helper maps a serialization failure into its io::Result instead of expecting. - complete_own_ip_attach switches its too_many_arguments allow to an expect with a reason. - the socketpair's second descriptor gets its own SAFETY comment. - the fake VM host's reply writes propagate a client that is already gone instead of discarding the must-use result.
…-box-on-the-host-and-hand-it-its-addre
|
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 |
There was a problem hiding this comment.
Review at 5cb14db against the T66 plan block and NET-138 (the requirement text lives in spec PR #1707, not yet on main).
Every layer obligation is implemented: the control socket (control.rs), host-side registration and allocation (box_registry.rs register_client_box / take_next), the CLI registering before create and carrying the pair in the request, and the daemon attaching with the handed address through attach_handed / IpAllocator::hand. The daemon half lives in net/mod.rs and net/provider.rs rather than the gvproxy_network.rs the plan named; that is the right home. The three plan tests are non-vacuous. The 17 out-of-layer files are 12 mechanical box_addresses: None literals and 5 pieces of wiring the plan omitted (starting the control server in run.rs, the shared BoxAddresses type and Record field, and the launcher threading). Justified; the draft is un-drafted once the blocking thread below is fixed.
One blocking defect (two allocators over one address run) and one design note, inline. Not asks for this PR: no withdraw verb exists yet, so destroyed boxes and failed creates leave live rows (T63 #1788 carries row withdrawal); UNREGISTERED_SOURCE_PHASE stays Announced although the gate doc says T66 flips it (not in this plan's layers; follow-up); the request carries the box name but no box id, which NET-133 will need. Size: plan est 280 code + 140 tests, PR is +1469; most of that is the literal fallout and tests.
|
/revise Round 1, bounded to two items from review 5379008672. Nothing else is in scope for this round.
Re-run the three plan test lines at the new head. |
…0s ran out the coder's uncommitted work as it stood when its 7200s ran out The coder was stopped at the end of its time with these changes not committed. The runtime committed its working tree as it stood so the work is kept: this commit is a snapshot, not a finished step, and may not build or pass its tests.
…-box-on-the-host-and-hand-it-its-addre
|
Round 1 of 10 on a review requesting changes, pushed as The coder ran out of its 7200s before it finished. If it left work uncommitted, its last commit is the runtime's snapshot of that work as it stood — the commit message says so — and not a finished step: it may not build or pass its tests.
Verify Outside the task's
Converted to a draft: something above needs a person before review. On a VM-backed host, activate registers the new box with the VM host daemon on the host, the VM host daemon allocates the box's switch and loopback addresses into its table and hands them back, and the in-VM daemon attaches the box with the handed switch address and selects none. Commit headers reworded by the runtime to fit commitlint. The header the coder wrote is the first paragraph of each of these commits' bodies:
|
|
/revise Round 2, from the ARCH-EXPERT design ruling on round 1 (PASS-with-conditions). Bounded to five items; nothing else is in scope for this round.
Re-run the three plan test lines at the new head. |
The plan's PTask run is now two disjoint sub-runs: the daemon self-allocates task sandboxes and unregistered boxes from the lower half (net::self_allocation_run), and the VM host daemon hands registered boxes only from the upper half (box_registry::hand_out_run, the mirrored midpoint rule). Each side's tests pin the default plan's split literally, so the two statements cannot drift silently. IpAllocator::hand now refuses any recorded lease whatever drew it (a box attaches once per daemon life; a restart starts from an empty allocator, so the re-hand allowance and its handed list are gone) and refuses an address inside the reserve, where a host that disagrees about the split would put a box beside the daemon's own draws. Exhaustion stays an explicit error with no wrap or reuse on either side; the interim is stated in the IpAllocator and task_network docs — task sandboxes self-allocate from the reserve until the task registering every live box host-side retires daemon-side allocation — and the control socket's module doc states its v1 posture: the same-uid 0600 socket is the trust, requests are served serially with the 30 s read timeout.
A box whose VM host registration cannot be made runs with no host-side row: its frames are unattributed under the egress gate's announced interim, and the gate's per-box default fails closed for it when it binds (#1790). The warnings on the registration failure path — an unresolvable provider dir, a refusal, the deadline — now name the missing registration and that posture instead of the old egress-not-enforced phrasing.
clippy's manual_div_ceil flags (last - first + 1) / 2 in both mirrored split functions; (last - first).div_ceil(2) is the same number, since a PTask run's address span is always even. Doc: state that the hand-out run keeps one address more than the reserve.
|
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. Review threads the coder judged:
On a VM-backed host, Notes from the agent: Revise round 2 of 10; commits c797826, d662268, a0e0f96. All five directive items addressed: (1) split via mirrored self_allocation_run/hand_out_run with IpAllocator bounded at the reserve and take_next bounded at the hand-out run; (2) hand() now refuses any existing lease whatever its origin (handed list and re-hand allowance dropped) and refuses reserve addresses via the new HandedAddressInReserve; (3) interim stated in exec.rs's task_network and the IpAllocator/self_allocation_run docs; (4) all four activate failure warns name the unattributed interim and the #1790 fail-closed gate; (5) control.rs states the v1 posture (same-uid 0600 UDS, serial serving, 30 s read timeout). The split is deliberately mirrored, not centralized: crates/switch is outside this round's layers, so the rule lives once per daemon and each side's tests pin…[truncated] |
There was a problem hiding this comment.
Round 2 at a0e0f96 meets every ask and the ARCH-EXPERT conditions: disjoint self-allocation reserve and hand-out run on both sides, hand() refuses reserve and held addresses, interim and v1 control-socket posture stated, unattributed-box warn with #1790. Body rewritten by hand; the out-of-layer literal fixes are justified in it.
|
Reviewed: 1 finding, none blocking at Address the comments on the diff. Clobber: none: |
Re-attaching a handed (own-ip VM) box after its in-process host was rebuilt met HandedAddressCollision: hand() records the handed address into the allocator's table, detach() never removed a lease, so the lease outlived the teardown and the re-attach looked like a live attach. The session launcher threads the persisted box addresses into the respawn, so the box stayed unreachable until daemon restart. Key the collision to a live attach instead: detach now releases the handed lease it is given (a drawn lease is never released), and the OwnIp guard and abandon path pass their lease through. The same box re-attaching re-hands its address; a genuinely colliding attach is still refused. Addresses the review finding on IpAllocator::hand.
clippy-strict flags unwrap_used on changed lines; the lock is the one the abandon rewrite touched, so give it the same poisoned-mutex expect the rest of the strict-gated lines carry.
…-box-on-the-host-and-hand-it-its-addre
|
Round 3 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:
On a VM-backed host, activate registers the new box with the VM host daemon on the host, the daemon allocates the box's switch and loopback addresses into its table and hands them back, and the in-VM daemon attaches the box with the handed switch address and selects none — and now the same box re-attaching after its in-process host is torn down and rebuilt re-hands its address instead of meeting HandedAddressCollision, so the box is reachable again without a daemon restart. Notes from the agent: The review finding (PRRT_kwDOPdfcxM6oBxkQ) is valid and fixed by its first suggested framing: hand() still records the handed lease (so a live attach is refused), but detach() now releases the handed lease it is given via release_handed(); a drawn lease is never released. The OwnIp guard (gvproxy_network.rs) and the abandon path (provider.rs) pass their lease through to detach, so both production detach sites release. Two files are outside this task's layers: crates/minimald/src/net/mod.rs and crates/minimald/src/net/provider.rs — the finding points at the allocator in mod.rs itself, and the layer list predates the daemon-side allocator split from earlier rounds; gvproxy_network.rs is in-layer. Known residual: the rare wedged-host path (kill_and_stop_loop aborts the loop without NetGuard teardown) still holds the lease and refuses, by design — the collision guard stays meaningful for a live attach. Exec/task launches pass box_addresses: None, so they never hand and are unaffected. Round 3, thread judged: see threads. |
|
/revise Round 4, bounded to the items below; everything round 3 did stands.
Re-run the three plan test lines at the new head and refresh the Tests table. |
The merge of #1792 added the field to SessionConfig and every other initializer in the tree carries it; this one did not, so the macOS unit lane failed to compile the minvmd test target (E0063).
Per the ARCH-EXPERT ruling the lease's life is the attachment's for every attachment, handed or drawn alike: a task sandbox's self-drawn address ends with the sandbox the way a registered box's handed one ends with the box's attach. SwitchClient::detach withdraws the lease on every detach and IpAllocator::release_handed is renamed release; the handed set is unneeded and goes. The never-reuse rule (R1.6) is carried by the draw cursor, not by a lease the static-lease table keeps for a tap that is gone: the cursor never regresses, so a withdrawn drawn address is never re-drawn, and it is never handed either (the reserve hand refuses).
…-box-on-the-host-and-hand-it-its-addre
|
Round 4 of 10 on a /revise note, pushed as
Verify Outside the task's
Converted to a draft: something above needs a person before review. On a VM-backed host, activate registers the new box with the VM host daemon on the host, the VM host daemon allocates the box's switch and loopback addresses into its table and hands them back, and the in-VM daemon attaches the box with the handed switch address and selects none — and now every lease ends with its attach, handed or drawn alike, so the same box re-attaching re-hands its own address from the host's persisted row while a self-drawn address is never re-drawn, and the macOS unit lane compiles the minvmd test target again. Notes from the agent: Round-4 items: (1) added box_addresses: None to the SessionConfig initializer in vm_escape_integration.rs — the failing macOS unit lane's E0063; the minvmd test-line build compiles that target here. (2) IpAllocator::release_handed renamed release, the handed set removed, and SwitchClient::detach now withdraws the lease on every detach, handed or drawn alike, per the ARCH-EXPERT ruling; R1.6 now carried by the draw cursor (never regresses; a withdrawn drawn address is also refused by the reserve on hand). The adjusted handed_lease_releases_on_detach_and_the_box_rehands pins the drawn withdrawal (next draw is a higher address), the live-attach HandedAddressCollision refusal, and the re-hand. (3) attach_handed's doc now states the re-handed address comes from the host's persisted row, that detach never touches that row, and that the host-row check by box id (same address for the same box, refusal on mismatch) lands with the box id in the registration (T44, #1660, NET-133), which names it as the gap — that doc statement went into the item-2 commit, since both items rewrite the same attach_handed paragraph. gvproxy_network.rs (OwnIpGuard/lease_ip docs) and provider.rs (abandon doc) got doc-only consistency touches describing the new release rule: gvproxy_network.rs is a task-layer file, provider.rs is outside this task's layers but inside this PR's changed set; no code there changed. First run of the adjust…[truncated] |
|
/revise Round 4 landed every earlier ask; lanes are green at 688b0bc. One design condition from the ARCH review of T63 (#1788) lands on this task, because it is the creator-side half of the same rule: the host-side row's creator is its withdrawer (deployment-and-egress-gateway.md v0.9 §5.5, §7.1 "withdrawn at box end", §4.2 "withdrawal precedes reuse").
Re-run the plan's lines after the change:
|
The destroyed or failed activation's client withdraws the row its registration bought: a tagged request envelope (register | withdraw), a withdrawal answered with the pair echoed back, and the pair-proven removal in the registry — lookup, proof, and removal under one write of the row lock, no row at the address answered as the success it is. The withdrawn addresses are not returned to the cursors; what their frames do next stays the gate phase's to say, both arms pinned at relay level through the phase parameter in host_row_withdrawn_on_destroy. The unregistered-source phase flip is not this change: it lands with the last row source (T66's follow-up), and the docs on register_client_box and the control module say so.
The session's creator now speaks the withdraw verb everywhere the registration bought a row it no longer holds: at destroy (single and --all, whose per-session record fetch carries the pair), at every activation failure after registering — create transport and daemon refusals, the autogen retry's abandoned row, the version-skew bail, compose and verdict failures, the finalize teardown, the Ctrl-C guard — and the registration itself moved to just before the create, the last fallible client-side step, so the checks between config and create can no longer strand a row. Withdrawals are best-effort and quiet when nothing is owed; the Ctrl-C guard fetches the record before aborting, which may take the pair with the session. cmd/mod.rs and task.rs are outside this task's layers; the guard's withdrawal required the former (the signature is unchanged, so task sessions need no edit) — noted in result.json.
|
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. Review threads the coder judged:
On a VM-backed host, activate registers the box with the VM host daemon, the daemon allocates its switch and loopback addresses into its table and hands them back, the in-VM daemon attaches with the handed switch address — and now the same client withdraws that row when the session is destroyed or the activation fails after registering: Notes from the agent: Directive asks 1-3 done in five commits on the branch. Layer notes: (a) crates/minimal/src/cmd/mod.rs is outside the layers but the Ctrl-C guard's withdrawal required it — arm_activation_interrupt gained an in-task record fetch (before the abort, which may take the pair with the session) plus the withdraw call; its signature is unchanged, so task.rs and the tests.rs connection inventory needed no edit, and both verify (inventory test listed in tests). (b) The registration call moved from pre-config to just before the create loop — the last fallible client-side step — so the config/loadout/hook/daemon-connect failures between them can no longer strand a row; this is a behavior-order change worth a reviewer's eye (the control socket already exists by then: ensure_daemon autospawns at function start). (c) The directive's test wording 'the gate refuses a publish at the withdrawn switch address' is only true under the InForce phase; the shipped Announced interim admits a withdrawn in-plan address with its rate-limited warn, so host_row_withdrawn_on_destroy pins both arms via gate_over_with_phase's phase parameter — the flip the follow-up lands breaks nothing here. (d) just clippy-strict (local-only gate): one hit, unwrap_in_result on…[truncated] |
There was a problem hiding this comment.
Round 5 lands every ask: the withdraw verb with creator and pair checks, the CLI withdrawing on destroy and on both failed-create paths, the phase-flip ownership note, and host_row_withdrawn_on_destroy. ARCH conditions met; minimal-review concluded on this head with one LOW deferred to #1797.
|
Round 6 of 10 on the runtime's review findings, pushed as Resolved conflicts in:
Verify Outside the task's
Converted to a draft: something above needs a person before review. On a VM-backed host, activate registers the new box with the VM host daemon on the host, the VM host daemon allocates the box's switch and loopback addresses into its table and hands them back, and the in-VM daemon attaches the box with the handed switch address and selects none — unchanged by this round, which only makes a refused registration stop burning a hand-out address. Notes from the agent: Round 6 delta: the one open thread, fixed in crates/minvmd/src/box_registry.rs. register_client_box drew the switch address before the loopback address, so a LoopbackExhausted refusal advanced the switch cursor and burned a hand-out address no row was ever published at; the spent-for-good rationale covers only addresses a row held. The draws are swapped (loopback first — the slice is the run a host exhausts, 32 addresses per switch against a hand-out run of at least 126, so an exhausted slice refuses before the switch cursor moves), the spent-for-good doc paragraph is rescoped to published addresses and names the plan-shape dependency (every planned hand-out run outnumbers its slice, which keeps the reverse exhaustion out of reach), and the carved-/24 test pins the refusals leaving the switch cursor exactly where the 32 successes left it. Happy-path behaviour is unchanged, so the proof above still holds. Also ran beyond the test lines: cargo nextest run -p minvmd box_registry:: (3 passed), cargo clippy -p minvmd --all-targets (clean), just clippy-strict (clean, scoped to the branch's changed crates), cargo fmt --all -- --check (clean). |
…ost at merge T66 (#1789) added box_addresses to SessionConfig and SandboxLauncher; #1768 and #1765 each added an initializer without it on a base that predated the field, and their green lanes never re-ran against the merged main. The minimald test targets no longer compile on main; this fills the field the way every other initializer does. The merge queue this PR enables is what catches that class of stale green before it lands.
* ci: trigger the required lanes on merge_group A merge queue on main needs the five required aggregators to report on every queue commit. Each lane already gates its jobs on `github.event_name != 'pull_request' || <changes output>` and runs the dorny filter only on pull_request, so a queue commit runs the full set, which is what docs/ci-strategy.md section 5 prescribes for the queue. No job logic changes. * fix(minimald): fill box_addresses in the two test initializers main lost at merge T66 (#1789) added box_addresses to SessionConfig and SandboxLauncher; #1768 and #1765 each added an initializer without it on a base that predated the field, and their green lanes never re-ran against the merged main. The minimald test targets no longer compile on main; this fills the field the way every other initializer does. The merge queue this PR enables is what catches that class of stale green before it lands. * ci: pin the merge_group trigger to main and release branches The sibling push and pull_request triggers in all five lanes pin branches to main and release/**; the merge_group trigger now matches them, so a queue opened on any other branch does not run the lanes.
Main's box registration (#1789) added a `box_addresses` field to `SessionConfig`, and the NET-068 harness's `CreateSession` initializer went without it. The macOS lane is the only one that compiles the harness (the `minvmd_libkrun` cfg the file is gated on is unset on stock Linux CI, where the file compiles to nothing), so the Linux lanes stayed green while the macOS `unit` job failed at clippy with E0063, reding `ci-macos-success` with it. Send `None`: no registration happens on this path, the box attaches as an unregistered one always has, drawing its own switch lease — the same value every sibling minvmd harness sends.
…al#1771) * test(minimald): hostname-only toolchain allowlist admitted by DNS gate (NET-068) * test(minvmd): hostname-only allowlist toolchain completes inside a VM box (NET-068) * fix(minvmd): use minvmd::cmd in integration test instead of crate::cmd The integration test crate cannot resolve crate::cmd::own_ip_requested. Use the public minvmd::cmd::own_ip_requested path instead. Review: PRRT_kwDOPdfcxM6ngj6Y * fix(minvmd): address review threads in NET-068 egress allowlist integration test - Move the toolchain hostname list into sessions::NET068_TOOLCHAIN_EGRESS_HOSTS so the minimald unit fixture and the minvmd integration test share it. - Fix integration-test compile issues: use sessions::SessionId, remove private minimald_rpc::SessionId, gate on MINVMD_GVPROXY_BIN instead of MINVMD_VM_OWN_IP, and fix FinalizeSession error typing. - Upload a minimal.toml with toolchain packages before ConfigureLoadout; replace podman pull with skopeo copy; add per-tool timeouts and small git clone target. - Read MINVMD_BOOT_LOG (falling back to boot.log) and boot with RUST_LOG=info,minimald::net::dns_gate=debug for diagnostics. - Document that the DNS gate lives in the guest minimald and that the host gvproxy is the L2 switch. - In dns_gate.rs, reword the negative assertion to name the design divergence and note that the TCP-only fixture still resolves because of the UDP/53 resolver carve-out. * docs(sessions): correct EgressPolicy doc comment to say egress * fix(minvmd): make NET-068 integration harness match the composed box The composed box has no Debian userland or apt package, so drop the apt-get leg and document the NET-068 deviation. Use pip3 (the only pip in the python package) and skopeo --insecure-policy (no containers policy.json ships). Cap per-tool exec at 60s and make the boot-log tail robust to invalid UTF-8 while filtering to dns-gate/policy lines so a chatty tool cannot push the admission evidence out of the cap. * chore(sessions,build): make NET-068 fixture internal and give test-vm a gvproxy export Hide the shared NET068_TOOLCHAIN_EGRESS_HOSTS constant from public docs. Export MINVMD_GVPROXY_BIN in both test-vm recipes so the own-IP integration tests can run, and add a vm-profile override for hostname_allowlist_toolchain_completes so the 6-minute default kill does not cut off a real toolchain run. * test(minvmd): drop the apt leg from the NET-068 toolchain run Spec PR gominimal#1707 amended NET-068 to the four-tool toolchain (git clone, npm install, pip install, a container pull): the composed box has no Debian userland and no upstream package carries apt, so an apt leg can never exit 0 there. - Module doc and in-file comments now state the four-tool toolchain and no longer claim apt is asserted or recorded as a deviation; the spec matches the test. - `deb.debian.org` and `security.debian.org` leave `sessions::NET068_TOOLCHAIN_EGRESS_HOSTS`; the only other user, the `minimald` DNS-gate unit test `toolchain_hosts_admitted_by_gate`, drops the same two rows. - The boot-log tail no longer keys on the `dns-gate` component string, which the guest console need not print; it matches the gate's message text alone (`admitted`, `refused`, `rule_matched`) and, when nothing matches, returns the last 40 unfiltered lines so the console stays visible. * test(minvmd): bring the NET-068 harness up through the supervisor path `minvmd boot --foreground` never spawns the host gvproxy switch (only the `run` supervisor does), so the shuttle bridge dialled a socket nothing served and the guest had no egress: the composer could not fetch the upstream packages and every toolchain leg failed to spawn before the allowlist was exercised. - `Guest::boot` now runs `minvmd run --detach --timeout 150`, polls `status --json` until Running, and stops the VM on drop, bounded like the sibling harnesses; boot failures quote the supervisor's `run.log`. - With the packages composed, `ConfigureLoadout` routes their env wiring back as `Pending`; the harness gates it with the `sessions` client handler and an approve-all `PolicyHooks`, then `SubmitVerdict`. An `Approved` patch would need the patches upload the CLI performs, so it is reported instead. - `camino` joins minvmd's dev-dependencies: the `PolicyHooks` trait names `Utf8Path` in its signatures. * test(sessions,minvmd): admit Docker Hub's CloudFront blob CDN in the NET-068 run The container pull stalled under the hostname-only allowlist: Docker Hub's blob fetch 307-redirects to `production.cloudfront.docker.com` (18.238.0.0/16), which the list did not name, so the relay refused the flow as `egress-undeclared-subnet`. Docker Hub has served blobs from either of its CDNs, so the list now carries both `production.cloudflare.docker.com` and `production.cloudfront.docker.com`, with a doc comment saying why; the `minimald` DNS-gate unit table mirrors the row. Also read the status JSON with `.get(..)` in the harness's Running poll instead of indexing, clearing the strict-lint hit the previous commit introduced. * docs(minvmd): state the no-apt facts beside the NET-068 four-tool run Thread PRRT_kwDOPdfcxM6nlsFW asked the PR to name which package would carry apt: none does. gominimal/pkgs@f4de33d0 is not a Debian userland and no upstream package carries apt, so the pre-gominimal#1707 apt leg could never exit 0 in the composed box. The module doc now states those facts; the PR body statement is carried by this round's result. * test(minvmd): add box_addresses to the NET-068 session create Main's box registration (gominimal#1789) added a `box_addresses` field to `SessionConfig`, and the NET-068 harness's `CreateSession` initializer went without it. The macOS lane is the only one that compiles the harness (the `minvmd_libkrun` cfg the file is gated on is unset on stock Linux CI, where the file compiles to nothing), so the Linux lanes stayed green while the macOS `unit` job failed at clippy with E0063, reding `ci-macos-success` with it. Send `None`: no registration happens on this path, the box attaches as an unregistered one always has, drawing its own switch lease — the same value every sibling minvmd harness sends. * test(minvmd): approve only the named pending var and report skips in the NET-068 run The compose gate now approves exactly NPM_CONFIG_CACHE, the one variable the node package wires through env_state_wiring, and fails the test naming any other var, patch, or hook the daemon routes back. A missing precondition (MINVMD_E2E != 1 or no MINVMD_GVPROXY_BIN) prints one "SKIPPED: hostname_allowlist_toolchain_completes: ..." stderr line and returns; under MINVMD_VM_LANE it panics instead, so a VM lane cannot go green without the host switch. * test(minimald): hold the gate's toolchain names to the NET-068 constant The gate test pairs each host with a representative address, so it cannot read its names off the shared constant. It now asserts its names equal the constant, so a host added there fails here until the test exercises it. * test(minvmd): track the boot-log tail offset in raw bytes The offset was taken from a lossy-decoded copy, where each invalid byte becomes a three-byte replacement character, so the next read could skip new bytes or slice inside a character. Slice the raw bytes, then decode. * test(minvmd): pin the guest READY wait to the cold-boot timeout The detach timeout bounds only the poll for Running; the VMM parent's READY wait reads MINVMD_READY_TIMEOUT_SECS (60 s default). Set it in the harness so a bare nextest run tolerates a 40-70 s cold boot. --------- Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
T66 — Register a box on the host and hand it its addresses
slice: S9
kind: task
covers: NET-138
size: M
est: 280 code, 140 tests
executor: agent
difficulty: needs care
model: strongest
diagnostics: the bundle's VM host daemon log names each registration with the box id and the addresses allocated; the daemon log names the handed address at attach
observability: one info log line per registration with the box id and its two addresses; one debug line per attach naming the handed address
layers:
- vmhost:
- crates/minvmd/src/control.rs (new): the host-side control socket the client registers a box on and reads its addresses from
- crates/minvmd/src/box_registry.rs: a registration fills the box's row from the client's expanded spec and allocates its switch and loopback addresses from the helper's slice
- crates/minvmd/src/lib.rs: declares the control module
test: cargo nextest run -p minvmd box_addresses_allocated_on_host_and_handed_to_daemon
- cli:
- crates/minimal/src/cmd/session.rs: on a VM-backed host, activate registers the new box (id, name, ingress rules, egress rules) with the VM host daemon over its control socket, receives the box's switch and loopback addresses, and passes them in the session create request
test: cargo nextest run -p minimal activate_registers_box_with_vm_host
- daemon:
- crates/minimald-rpc/src/lib.rs: the session create request carries the handed switch and loopback addresses
- crates/minimald/src/net/gvproxy_network.rs: an own-address attach configures the tap with the handed address and selects none
test: cargo nextest run -p minimald own_ip_attach_uses_handed_address
produces:
- crates/minvmd/src/control.rs
consumes:
- crates/minvmd/src/box_registry.rs
proof: On a VM-backed host, activate registers the new box with the VM host daemon on the host, the VM host daemon allocates the box's switch and loopback addresses into its table and hands them back, and the in-VM daemon attaches the box with the handed switch address and selects none.
depends on:
- blocked by #1640
Proof
On a VM-backed host,
min session activate --network own-ipregister 8000 s the new box (name, expanded ingress ports, declared egress policy) with the VM host daemon over a same-uid 0600 control socket beside the ssh socket. The host daemon allocates the box's switch and loopback addresses from the upper half of the plan's PTask run with a monotone cursor, fills the row from the client's expanded spec, and hands the pair back; the create request carries it, and the in-VM daemon attaches the tap at the handed address (IpAllocator::hand) and selects none. The lower half of the run is the in-VM daemon's self-allocation reserve (task sandboxes and unregistered boxes), so a handed address can never collide with a drawn one; a handed address an attach still holds is refused, never shared.Withdrawal. The creator withdraws the row it registered:
min session destroy, and every activate path that fails after registration, sends thewithdrawverb with the box's name and handed pair, and the host daemon removes the row only when the pair matches the creator's row (WithdrawErrorotherwise). Addresses are not reused (the cursor never regresses); withdrawal ends the row's admissions. The daemon-side lease ends with its attach, handed or drawn alike, so the static-lease table holds only live taps; the host row is untouched by detach.Interim, stated in the code. Control-socket trust is the socket's 0600 mode with no peer-credential check, served serially with a 30 s read timeout. An absent socket degrades an own-ip activate to unregistered with a warning rather than failing it. The gate's
UNREGISTERED_SOURCE_PHASEis not flipped here: the flip belongs to the task that lands the last row source, and the host-address cohort row is not yet planned. The host-row check by box id lands with T44 (#1660, NET-133).Design review: ARCH-EXPERT ruled on the two-cursor split, the control-socket posture and the withdraw ownership (creator withdraws its own row; T66, not T44); every condition landed by round 5.
Out of layer, justified
The create request's new
box_addressesfield forced theSessionConfig/Recordconstructor literals across the CLI, TUI, daemon store and test harness to name it (Noneeverywhere but the own-ip activate path and the store samples). The withdraw verb addedcrates/minimal/src/cmd/mod.rs(the control-socket client helper) beside the plan'ssession.rs.Tests
cargo nextest run -p minvmd box_addresses_allocated_on_host_and_handed_to_daemoncargo nextest run -p minimal activate_registers_box_with_vm_hostcargo nextest run -p minimald own_ip_attach_uses_handed_addresscargo nextest run -p minvmd host_row_withdrawn_on_destroyVerify
min task run agent-verifypassed. Every lane is green at the head.Closes #1711