8000 T31 — Resolve and reach sibling boxes by name under both boxes' rules by minimal-foundry[bot] · Pull Request #1766 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

T31 — Resolve and reach sibling boxes by name under both boxes' rules - #1766

Merged
norrietaylor merged 13 commits into
mainfrom
agent/1647-resolve-and-reach-sibling-boxes-by-name-under-bo
Sep 30, 2026
Merged

norrietaylor merged 13 commits into
mainfrom
agent/1647-resolve-and-reach-sibling-boxes-by-name-under-bo

Conversation

@minimal-foundry
Copy link
Copy Markdown
Contributor

T31 — Resolve and reach sibling boxes by name under both boxes' rules

slice: S7
kind: task
covers: NET-072, NET-073
size: M
est: 200 code, 180 tests
executor: agent
difficulty: needs care
model: standard
diagnostics: the bundle's daemon log tail carries each box-to-box refusal with both boxes and the rule
observability: one debug log line per box-zone connection with the source box, the target box and each side's verdict
layers:
- core:
- crates/sessions/src/core/egress.rs: zone names need no allow entry and zone answers are carved out of the infrastructure deny set
test: cargo nextest run -p sessions box_zone_answers_carved_out_of_infrastructure_deny
- daemon:
- crates/minimald/src/net/dns_gate.rs: skips the allow-list check for the zone
- crates/minimald/src/net/dns.rs: exposes the zone suffix to the gate
- crates/minimald/src/net/switch.rs: checks the source's egress at connect beside the target's ingress gate
- crates/minimald/src/net/policy.rs: registers sibling names in the zone
test: cargo nextest run -p minimald box_zone_resolution_needs_no_allow_entry
test: cargo nextest run -p minimald box_zone_connection_enforced_at_connect
produces: none
consumes:
- crates/minimald/src/net/dns_gate.rs
proof: A box resolves a sibling's name with no allowlist entry, and a connection to it succeeds only when the target's ingress rules and the source's egress rules both allow it.
depends on:
- blocked by #1638

Out of time

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. So this PR is a draft.

Proof

A box resolves a sibling's name with no allowlist entry, and a connection to it succeeds only when the target's ingress rules and the source's egress rules both allow it.

Commit headers reworded by the runtime to fit commitlint. The header the coder wrote is the first paragraph of each of these commits' bodies:

  • feat(minimald): the coder's uncommitted work as it stood when its 7200s ran out

Tests

line exit tail
cargo nextest run -p sessions box_zone_answers_carved_out_of_infrastructure_deny 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-d75a... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/aacba/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/aacba/tree/crates/ot) C
cargo nextest run -p minimald box_zone_resolution_needs_no_allow_entry 101 te/minimal/sessions/5e21a/tree/crates/mfile) Compiling minimald-rpc 8000 v0.0.1 (/home/runner/.local/state/minimal/sessions/5e21a/tree/crates/minimald-rpc) Compiling decode v0.0.1 (/home/runner/.local/state/minimal/sessions/5e21a/tree/crates/decode) Compiling checkouts v0.0.1 (/home/runner/.loca
cargo nextest run -p minimald box_zone_connection_enforced_at_connect 101 e/runner/.local/state/minimal/sessions/a3416/tree/crates/stdlib) Compiling version v0.7.0 (/home/runner/.local/state/minimal/sessions/a3416/tree/crates/version) Compiling mlog v0.0.1 (/home/runner/.local/state/minimal/sessions/a3416/tree/crates/mlog) Compiling decode v0.0.1 (/home/runner/.l

Verify

min task run agent-verify exited 101, so this PR is a draft.

state/minimal/sessions/82327/tree/crates/graph)
    Checking lcache v0.0.1 (/home/runner/.local/state/minimal/sessions/82327/tree/crates/lcache)
    Checking op v0.0.1 (/home/runner/.local/state/minimal/sessions/82327/tree/crates/op)
    Checking rcache v0.0.1 (/home/runner/.local/state/minimal/sessions/82327/tree/crates/rcache)
    Checking orchestrator v0.0.1 (/home/runner/.local/state/minimal/sessions/82327/tree/crates/orchestrator)
    Checking check v0.0.1 (/home/runner/.local/state/minimal/sessions/82327/tree/crates/check)
    Checking mctx v0.0.1 (/home/runner/.local/state/minimal/sessions/82327/tree/crates/mctx)
    Checking minimald v0.7.0 (/home/runner/.local/state/minimal/sessions/82327/tree/crates/minimald)
error[E0015]: cannot call non-const associated function `std::collections::HashMap::<std::net::Ipv4Addr, std::sync::Weak<net::switch::SessionGate>>::new` in constants
   --> crates/minimald/src/net/switch.rs:815:76
    |
815 | const LIVE_GATES: Mutex<HashMap<Ipv4Addr, Weak<SessionGate>>> = Mutex::new(HashMap::new());
    |                                                                            ^^^^^^^^^^^^^^
    |
    = note: calls in constants are limited to constant functions, tuple structs and tuple variants

error[E0308]: mismatched types
    --> crates/minimald/src/net/switch.rs:1014:36
     |
1014 |         let Some(peer) = live_gate(pkt.src.ip()) else {
     |                          --------- ^^^^^^^^^^^^ expected `Ipv4Addr`, found `&Ipv4Addr`
     |                          |
     |                          arguments to this function are incorrect
     |
note: function defined here
    --> crates/minimald/src/net/switch.rs:832:4
     |
 832 | fn live_gate(addr: Ipv4Addr) -> Option<Arc<SessionGate>> {
     |    ^^^^^^^^^ --------------
help: consider dereferencing the borrow
     |
1014 |         let Some(peer) = live_gate(*pkt.src.ip()) else {
     |                                    +

error[E0716]: temporary value dropped while borrowed
   --> crates/minimald/src/net/switch.rs:823:21
    |
823 |     let mut gates = LIVE_GATES.lock().expect("live gate table poisoned");
    |                     ^^^^^^^^^^                                          - temporary value is freed at the end of this statement
    |                     |
    |                     creates a temporary value which is freed while still in use
824 |     if gates.len() >= LIVE_GATES_SWEEP_AT {
    |        ----- borrow later used here
    |
help: consider using a `let` binding to create a longer lived value
    |
823 ~     let binding = LIVE_GATES;
824 ~     let mut gates = binding.lock().expect("live gate table poisoned");
    |

Some errors have detailed explanations: E0015, E0308, E0716.
For more information about an error, try `rustc --explain E0015`.
error: could not compile `minimald` (lib) due to 3 previous errors
warning: build failed, waiting for other jobs to finish...
error: could not compile `minimald` (lib test) due to 3 previous errors

Base

The base branch moved while the coder worked and was merged in before the tests ran. These files changed on BOTH sides since the branch point — read them first, because a rewrite from a stale reading can undo a merge without a conflict:

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

Closes #1647

… scan

The egress verdict's infrastructure scan refused every address in the
switch's reserved plane, including the box-zone addresses a guest must
reach. Move the plane out of the always-refused set so a box-zone answer
declared as such is scanned against the fixed infrastructure addresses
only, while every other answer keeps the plane refused.
…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.
@coderabbitai
coderabbitai Bot commented Sep 30, 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: bdc9cbc8-4d1d-42cb-840e-ec1815c7e9b6

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
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on a failed check suite: the coder changed nothing. every commit this round put on the branch above 0e0b44ce4429 is a merge of the base branch (319479ad8c5d), so the pull request carries no work from it.

The snapshot's NET-073 connect-time check was left with three compile
errors: a `HashMap` built in a `const` item, which needs a `LazyLock`
static instead, and the source lookup passing `&Ipv4Addr` where the
table is keyed by value. The whole PR's check suite was red on the
build, so nothing could run.
The daemon layer's half of T31: two proofs for NET-072 and NET-073
against the real relays.

`box_zone_resolution_needs_no_allow_entry` (dns_gate) drives a box that
allows `github.com` and, by address, nothing else through a resolution
of its sibling's zone name: the query rides the resolver carve-out, the
reply passes through to the box, and the sibling's lease is pinned and
reachable with no allowlist entry naming it and no subnet declaring
it. The carve-out is then proved to be the zone's alone — an ordinary
name with no entry resolves but admits nothing, and a lookalike that
merely ends in the zone's words (a distinct address, since the
admission table is keyed by address) pins nothing either.

`box_zone_connection_enforced_at_connect` (switch) puts two boxes'
relays live at once and decides the same connection four ways at the
target's relay, on the SYN itself: refused by the source's own egress
rules before the resolution; admitted by both halves after it (the
source's pin beside the target's declared port); refused by the
target's ingress gate alone when the source's half admits the address
but the port is undeclared; and untouched for a source that is not a
live box on this daemon's switch, which keeps the target-ingress-only
behavior. Each connection says one debug line naming the source box,
the target box and each side's verdict, and each refusal also says a
warn line under the refusing box's label with the rule — the two lines
the diagnostics bundle's daemon log tail is read for.

Along the way: `is_zone_name` gets the unit test that pins its
boundary (the apex and its names, never a name that merely carries
the zone's words), and the DNS gate's test harness items the switch
proof needs (the resolver address, the DNS frame builders, the box-end
reader) are shared as `pub(crate)` rather than copied — the same
sharing direction the switch's harness already gives the gate's tests.

The harness's global capture reads at INFO, so the switch proof
installs its own thread-local capture at DEBUG for the connect-time
line.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 2 of 10 on a failed check suite, pushed as 859677c9bfbb.

line exit tail
cargo nextest run -p sessions box_zone_answers_carved_out_of_infrastructure_deny 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-8147... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/1e0e7/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/1e0e7/tree/crates/ot) C
cargo nextest run -p minimald box_zone_resolution_needs_no_allow_entry 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-41fc... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/790c3/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/790c3/tree/crates/ot) C
cargo nextest run -p minimald box_zone_connection_enforced_at_connect 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-39b4... Compiling stdlib v0.0.21 (/home/runner/.local/state/minimal/sessions/fc872/tree/crates/stdlib) Compiling version v0.7.0 (/home/runner/.local/state/minimal/sessions/fc872/tree/crate

Verify min task run agent-verify passed.

A box now resolves a sibling's zone name with no allowlist entry for it — the query rides the resolver carve-out, the reply passes through to the box, and the sibling's lease is admitted as reach — and a connection to it succeeds only when the target's ingress rules and the source's egress rules both allow it: decided at the target's relay on the SYN itself (the source's half looked up by lease in LIVE_GATES, with the DNS pin the source's own relay granted), each connection saying one debug line naming the source box, the target box and each side's verdict, and each refusal a warn line under the refusing box's label with the rule — all four combinations proved end to end against two live relays, plus the fabric's own traffic left to the target's ingress decision alone.

Notes from the agent: The round opened with the whole workspace failing to compile — the cause of every red check in the directive: the snapshot's LIVE_GATES table in switch.rs had three errors (a HashMap::new() in a const, a &Ipv4Addr passed where Ipv4Addr was expected, and the borrow that followed the const). Fixed as a static LazyLock plus the deref, commit 25c558f. After it: cargo clippy -p minimald --all-targets --locked -- -D warnings is clean, the full minimald suite (496 tests) passes, cargo fmt --all applied, and just clippy-strict is clean over its derived scope (-p minimald -p sessions). The two minimald test lines named by the task did not exist at the branch head; they are written in commit 859677c, together with a unit test for dns.rs's is_zone_name boundary (the apex and its names, never a lookalike that merely ends in the zone's words) and the pub(crate) sharing of the DNS gate's test harness items the switch proof needs — the same sharing direction the switch's harness already gives the gate's tests. The zone-lookalike case answers with a distinct address because the admission table is keyed by address, not name. The connect-time proof installs its own thread-local ca…[truncated]

@norrietaylor norrietaylor left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/revise one narrowing before this PR can leave draft; everything else in the T31 contract is met (all four layers present, policy.rs needed nothing because register_dns_name already puts sibling names in the zone on main, the three named tests are real and on-requirement, the connect-time debug line carries source, target and both verdicts, and Kani re-proves the non-zone decision unchanged).

MEDIUM: the zone carve-out admits a zone-name answer at any non-infrastructure address, not only a plane lease. rebinding_admits (crates/sessions/src/core/egress.rs:728-730) skips the 100.64.0.0/10 plane check when box_zone is set, but nothing then requires the answer to be inside the plane: a *.min.internal answer of 10.1.2.3 or a public address passes, and the sessions test asserts exactly that (egress.rs:1426, the private-zone-answer case). Because allows_name (crates/minimald/src/net/dns_gate.rs:342-347) now auto-allows every *.min.internal question, the gate pins every A record in such a reply (dns_gate.rs:461-482) with no allow entry from the box. The answerer serves its zone for A and forwards other types upstream (NET-136 intercepts only AAAA/HTTPS/SVCB), so a zone question that reaches the upstream path and comes back carrying A records is pinned into the box's egress table. The design's carve-out is for box-zone names naming sibling leases, nothing wider.

Change:

  • In rebinding_admits, replace the if !box_zone && infrastructure.plane.contains(answer) branch with if box_zone != infrastructure.plane.contains(answer) { return Err(RebindingRefusal::Infrastructure); } so a zone answer is admitted only inside the plane and a non-zone answer only outside it.
  • Update the sessions test's private-zone-answer case to expect Err(RebindingRefusal::Infrastructure), and add the positive case: a zone answer inside the plane is admitted.
  • Change the Kani oracle line (egress.rs:1739, let plane = !box_zone && ...) to box_zone != plane.contains(answer) so the proof keeps matching the code.
  • The three named test lines must still pass; run them.

Two LOW items to note in the PR body, no code change required: the target-side source check cannot refuse in production because the source relay's own egress leg already drops under the same rules (the test proves it by injecting on the target's switch side), and LIVE_GATES is process-global keyed by lease, so tests that register the same lease share it. Keep the commits already on the branch.

@norrietaylor
Copy link
Copy Markdown
Member

/revise one narrowing before this PR can leave draft; everything else in the T31 contract is met (all four layers present, policy.rs needed nothing because register_dns_name already puts sibling names in the zone on main, the three named tests are real and on-requirement, the connect-time debug line carries source, target and both verdicts, and Kani re-proves the non-zone decision unchanged).

MEDIUM: the zone carve-out admits a zone-name answer at any non-infrastructure address, not only a plane lease. rebinding_admits (crates/sessions/src/core/egress.rs:728-730) skips the 100.64.0.0/10 plane check when box_zone is set, but nothing then requires the answer to be inside the plane: a *.min.internal answer of 10.1.2.3 or a public address passes, and the sessions test asserts exactly that (egress.rs:1426, the private-zone-answer case). Because allows_name (crates/minimald/src/net/dns_gate.rs:342-347) now auto-allows every *.min.internal question, the gate pins every A record in such a reply (dns_gate.rs:461-482) with no allow entry from the box. The answerer serves its zone for A and forwards other types upstream (NET-136 intercepts only AAAA/HTTPS/SVCB), so a zone question that reaches the upstream path and comes back carrying A records is pinned into the box's egress table. The design's carve-out is for box-zone names naming sibling leases, nothing wider.

Change:

  • In rebinding_admits, replace the if !box_zone && infrastructure.plane.contains(answer) branch with if box_zone != infrastructure.plane.contains(answer) { return Err(RebindingRefusal::Infrastructure); } so a zone answer is admitted only inside the plane and a non-zone answer only outside it.
  • Update the sessions test's private-zone-answer case to expect Err(RebindingRefusal::Infrastructure), and add the positive case: a zone answer inside the plane is admitted.
  • Change the Kani oracle line (egress.rs:1739, let plane = !box_zone && ...) to box_zone != plane.contains(answer) so the proof keeps matching the code.
  • The three named test lines must still pass; run them.

Two LOW items to note in the PR body, no code change required: the target-side source check cannot refuse in production because the source relay's own egress leg already drops under the same rules (the test proves it by injecting on the target's switch side), and LIVE_GATES is process-global keyed by lease, so tests that register the same lease share it. Keep the commits already on the branch.

The zone carve-out skipped the plane check outright, so a reply to a
*.min.internal question could carry any non-infrastructure address --
RFC 1918 or a public host -- and, because the daemon-side gate
auto-allows every zone question, pin it into the box's egress table
with no allow entry from the box. The design's carve-out is for box-
zone names naming sibling leases, which live in the plane; nothing
wider.

rebinding_admits now refuses the answer wherever the zone and the
plane disagree: a zone answer is admitted inside the plane and nowhere
else, an ordinary name's outside it and nowhere in it. The Kani
oracle restates the same equality so the proof keeps matching the
code, the sessions test asserts the refusal under every declaration
shape and adds the positive case across the plane's span, and the dns
gate's module doc names the narrowed semantics it composes with.

Refs: #1766
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 3 of 10 on a review requesting changes, pushed as f8345b5c8aa4.

line exit tail
cargo nextest run -p sessions box_zone_answers_carved_out_of_infrastructure_deny 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-df28... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/2c9ab/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/2c9ab/tree/crates/ot) C
cargo nextest run -p minimald box_zone_resolution_needs_no_allow_entry 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-aa92... Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/cdfec/tree/crates/ot) Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/cdfec/tree/crates/args) C
cargo nextest run -p minimald box_zone_connection_enforced_at_connect 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-bd2b... Compiling stdlib v0.0.21 (/home/runner/.local/state/minimal/sessions/7dbc8/tree/crates/stdlib) Compiling version v0.7.0 (/home/runner/.local/state/minimal/sessions/7dbc8/tree/crate

Verify min task run agent-verify passed.

A box still resolves a sibling's *.min.internal name with no allowlist entry, and a connection to it succeeds only when the target's ingress rules and the source's egress rules both allow it — and the answer a zone name can now pin is exactly a sibling's lease inside the 100.64.0.0/10 plane: any other address in a zone reply (RFC 1918, a public host) is refused by the rebinding intersection like any other infrastructure answer, whatever allow_subnets covers.

Notes from the agent: The review's MEDIUM is fixed in crates/sessions/src/core/egress.rs: rebinding_admits now returns RebindingRefusal::Infrastructure wherever the zone and the plane disagree (a zone answer only inside the plane, an ordinary name's only outside it), so the daemon-side gate — which auto-allows every *.min.internal question — can pin a zone reply's A records only where they are plane leases. The sessions test's private-zone-answer case now expects Err(RebindingRefusal::Infrastructure) under every declaration shape (open dimension, covering allow entry, deny-all declaration, plus a public answer), the positive case asserts admission across the plane's whole span, and the Kani oracle line is box_zone != plane.contains(answer) so the proof matches the code. Kani itself could not be run here (no cargo-kani on this box), so the oracle change is verified by inspection and the harness's equivalence claim rests with the ci-kani lane, which is advisory. The change is bit-identical for box_zone=false, so only the zone paths moved. dns_gate.rs changed only its module doc and one test doc comment, which still stated the pre-narrowing semantics. The two LOW items belong in the PR body, per the review, with no code change: (1) the target-side source check cannot refuse in production because the source relay's own egress leg already drops under the same rules — the test proves it by injecting on the target's switch side; (2) LIVE_GATES is process-global keyed by lease, so tests that register th…[truncated]

@norrietaylor norrietaylor left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 3 (f8345b5) applies the plane-only carve-out exactly as asked, with the test and the Kani oracle following; the design check on NET-072/NET-073 passed. One defect the widened allows_name introduces, in the thread below, and one comment ask.

Recorded, no change needed: letting the box's own deny_subnets refuse a zone answer at resolution is stricter than the design (which exempts zone resolution from egress rules and decides at connect); net reach is identical.

Comment thread crates/minimald/src/net/dns_gate.rs Resolved
Comment thread crates/minimald/src/net/switch.rs Resolved
`host.min.internal` is a zone name (NET-003), and inside an
own-address box its reply carries the switch's NAT'd host alias — an
address the infrastructure deny set names in its fixed range, because
the alias is infrastructure by design. The widened `allows_name`
from the box-zone carve-out therefore sent every host lookup through
the rebinding intersection: the mandated answer was refused as
infrastructure and the limiter wrote a false refusal line for each
box that resolved the host.

The row's reply now skips the pass entirely — it passes through to
the box, pins nothing, warns nothing. The reach it grants was never
the intersection's to give: reach over the name is local reach under
the box's own egress rules, decided at connect by the frame verdict.
A reply to the row's name carrying any other address is not the row
and still meets the intersection like any subverted zone answer.
The gate takes the subnet's host alias from the relay, and the
unit-test fixture's deny-set alias is corrected to the default /16
subnet's real one (100.64.255.254, not 100.64.0.254 — no test
depended on it).

`box_zone_resolution_needs_no_allow_entry` gains the host-row case:
the reply passes through byte-equal, the limiter emits no refusal
line, and a connection to the alias is refused — nothing is
admitted.

Also say in `refuse_zone_connect`'s doc, per review, why the
box-behind-another-daemon case is ingress-only: no such source can
arrive on this switch, not because its egress goes unchecked — each
daemon's gvproxy is its own L2 segment, and that box's host-address
reach rides the host loopback under the host-side classifier.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 4 of 10 on a review requesting changes, pushed as 9ce6a66b483b.

line exit tail
cargo nextest run -p sessions box_zone_answers_carved_out_of_infrastructure_deny 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-e71d... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/08e74/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/08e74/tree/crates/ot) C
cargo nextest run -p minimald box_zone_resolution_needs_no_allow_entry 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-09e9... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/4ab82/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/4ab82/tree/crates/ot) C
cargo nextest run -p minimald box_zone_connection_enforced_at_connect 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-e220... Compiling stdlib v0.0.21 (/home/runner/.local/state/minimal/sessions/56809/tree/crates/stdlib) Compiling version v0.7.0 (/home/runner/.local/state/minimal/sessions/56809/tree/crate

Verify min task run agent-verify passed.

Review threads the coder judged:

  • 👍 valid — crates/minimald/src/net/dns_gate.rs:378: The zone's host row now skips the rebinding pass in DnsGate::observe_response: a reply to host.min.internal whose answer is the subnet's host alias passes through to the box, pins nothing, and writes no refusal line, and box_zone_resolution_needs_no_allow_entry proves that case (reply byte-equal
  • 👍 valid — crates/minimald/src/net/switch.rs:1004: refuse_zone_connect's doc now says the box-behind-another-daemon case is ingress-only because no such source can arrive on this switch — each daemon's gvproxy is its own L2 segment and that box's host-address reach rides the host loopback under the host-side classifier (NET-078/NET-079) — not beca

A box resolves a sibling's name with no allowlist entry, and a connection to it succeeds only when the target's ingress rules and the source's egress rules both allow it — and since this round the zone's own host row behaves the same from the box's side: resolving host.min.internal (whose mandated answer is the switch's NAT'd host alias) passes its reply through with no false infrastructure refusal logged and nothing admitted, so a connection to the host remains a connect-time verdict under the box's own egress rules.

Notes from the agent: Round 4 (revise). Both threads judged valid and fixed in commit 9ce6a66. The MEDIUM fix is at the gate level, in minimald: the shared sessions intersection is deliberately unchanged this round, because carving the host alias out of InfrastructureDenySet there would admit it (the fabric plane contains the alias, box_zone=true) and pin host reach as though the host were a resolved sibling — the opposite of what NET-003 wants. Instead DnsGate takes the subnet's host alias from the relay (SessionGate::for_session passes subnet.host_alias().octets()) and observe_response skips the rebinding pass only for a reply to host.min.internal whose every answer is that alias: it passes through, pins nothing, warns nothing. A reply to the row's name carrying any other address still meets the intersection like any subverted zone answer. Also corrected the unit-test fixture gate_for: its deny-set host alias was 100.64.0.254, inconsistent with the default /16 switch subnet whose alias 8BD0 is 100.64.255.254 (verified against SwitchSubnet::default().host_alias()); no existing test depended on the old value, and the extended test asserts the derivation so the fixture cannot drift from the relay harness's gates. The switch.rs change is the requested comment clarification only (plus HOST_MIN_INTERNAL becoming pub(crate) so the gate reads one spelling of the row's name). cargo clippy -p minimald --all-targets clea…[truncated]

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

Approved: no findings at 94f9689ee0d8.

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 base-branch-moved warning names crates/minimald/src/net/dns.rs; git log on it shows this PR's commits ride on top of the T67/T9/T34/T27 merges, not under them, and the two-sided file's changes are additive (is_zone_name + its test). Nothing here reverts a recent fix.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

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

Round 5 of 10 on the runtime's review findings: the coder changed nothing. No code change, on purpose: the finding was a verification gap, not a defect — the E0015/E0308/E0716 fixes it names were already in the tree, every proof it doubted passes, so there is nothing to commit. The LIVE_GATES caution is contained by design and now by evidence: register_live_gate is called only from spawn_relay, the connect proof's leases (100.64.0.10/.11) are registered by no other proof, no other proof writes a bare TCP SYN into a relay's switch side, and both proofs also pass inside the single-process libtest target. That libtest run (cargo test -p minimald --lib net::) exits 1 on this branch (98 passed, 4 failed) and exits 1 on main with the same four tests failing (95 passed, 4 failed): allowed_egress_succeeds, egress_drop_logged_rate_limited, relay_rejects_non_lease_source, denied_range_resolution_logged assert exact counts on the harness's process-global log capture, which every concurrent sibling shares under libtest (test_harness.rs:68-73 documents that …[truncated]

@norrietaylor norrietaylor left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES — the KVM lane is red on every head of this PR, and the failure is a design conflict, not weather.

CI (Linux KVM) has failed on 0e0b44c, 319479a, 859677c, f8345b5 and 9ce6a66, each time at the landed NET-070 parity proof (scripts/session-e2e.sh, pair 5, "a caller whose egress rules deny the target", from #1730):

pair a caller whose egress rules deny the target
  direct:  GET http://e2e-par-own.min.internal:18087/ -> curl exit 0:
  proxied: GET http://e2e-par-own.min.internal:18087/ via the proxy at the daemon's switch address -> HTTP 403 ()
##[error]the denied caller's direct attempt did not end in a connect timeout (curl exit 0, expected 28) — the caller's egress gate did not drop it

(run 36695398819, job 109833892861)

The caller is an own-address box whose only allow entry is --allow-subnets 100.64.255.253/32; the target's lease is undeclared. Before this PR its SYN was dropped (exit 28). With this PR the connection completes, because refuse_zone_connect lifts an UndeclaredSubnet drop whenever peer.dns.admits_flow(destination) holds, and this PR both exempts box-zone resolution from the allow-list and admits the zone answer into the pin table — so resolving a sibling's name now grants its lease. That reads NET-072 (resolution needs no allow entry) as "resolution grants reach", which contradicts NET-070, NET-073 ("enforce … the source's egress rules at connection time") and this task's own proof line ("a connection to it succeeds only when the target's ingress rules and the source's egress rules both allow it"). Only an explicit deny_subnets entry still refuses a sibling.

Design ruling (ARCH-EXPERT, revising the earlier PASS): the zone-answer pin lift is a FAIL; the landed NET-070 proof is the correct oracle. The design's pin machinery has one entrance, §5.3 l145 (names matched by egress_allow_dns); zone names never take it (l152, §7.1 l249: exempt from allow-list matching because local names need no entry). A name the allow-list does not match creates no admission window, so at connect the source half is decided by egress_allow_subnets/egress_deny_subnets alone (l249: "source egress per UC6's local-reach semantics"; l248 is the same shape for box→host). Lifting an UndeclaredSubnet drop because the sibling's name resolved is a fourth admission path the design does not have, and it defeats default-deny for every sibling on the host (every box resolves *.min.internal by NET-072).

How a box declares sibling reach: an egress_allow_subnets entry covering the sibling's plane address (the plane 100.64.0.0/10 or the node's block; leases are routing, not identity, §4.2 l66), refined by egress_deny_subnets; the target's default-deny ingress gives per-target precision. An egress_allow_dns entry naming a sibling is a no-op. A name-scoped sibling grant would be an architecture change (file against gominimal/arch citing §7.1 l249 and §5.3 l145/l152), not something this PR can add.

What stays PASS: the allow-list exemption for zone resolution (NET-072); zone answers admitted only inside the plane as a rebinding rule, provided no pin is created from them; the conjunction on the bare SYN at connect; host and gateway sources ingress-only. Fix scope: refuse_zone_connect (drop the admits_flow lift), the egress.rs carve-out (validate the answer, do not enter it in the pin table), and the unit test. The KVM lane must be green before merge.

Comment thread crates/minimald/src/net/switch.rs Resolved
Comment thread crates/minimald/src/net/switch.rs Resolved
Comment thread crates/minimald/src/net/dns_gate.rs Resolved
@norrietaylor
Copy link
Copy Markdown
Member

/revise Round 6 was lost at the push (HTTP 403 on git-receive-pack after the work was complete), so nothing from it reached this branch; redo it against review 5365058842 on head 9ce6a66, whose threads are all still open.

The one blocking item: remove the pin lift in refuse_zone_connect (switch.rs, the DropReason::UndeclaredSubnet branch that passes a frame when peer.dns.admits_flow holds). Zone resolution stays exempt from the allow-list, but a zone answer must not create a pin: the source half of a sibling connect is decided by allow_subnets / deny_subnets only, and a sibling is reached by a CIDR entry, never by a name-scoped grant. Rewrite the NET-070 test as two cases (declared caller reaches the sibling by CIDR; caller with no CIDR grant is refused even after resolving the name) and make CI (Linux KVM) green on the NET-070 pair-5 proof, which is red on every head so far because the denied caller's direct connect completes.

`rebinding_admits`'s doc said the plane carve-out is "the reason a box
reaches a sibling by name with no `allow_dns_hosts` entry" — which is
the reading the connect-time gate was built on and the review rejected:
the intersection only splits the answer set, and reach is the caller's
to decide. State that split in the contract itself, and fix the test
comment that said the same ("a zone name pins nothing but a sibling's
lease") to the shape the daemon now honours.
The box zone's carve-out is the resolution's alone. A zone answer is
still validated by the rebinding intersection — each refusal is logged
like any other — but it never enters the pin table: the design's
admission windows come from `egress.allow_dns_hosts` matches alone
(§5.3), and a zone answer that became a pin was a name-scoped grant of
a sibling's lease. `refuse_zone_connect` drops its `admits_flow` lift
with it, so the source half of a sibling connect is the source's
`allow_subnets` / `deny_subnets` as declared and nothing beside them —
the one decision the source's own egress leg and the hostname proxy's
caller check both make of the same connection.

That is the pair the connect proof now makes: a caller whose
`allow_subnets` names the target's lease reaches it — its own leg
forwards the SYN and the target's relay admits both halves — while a
caller with no CIDR grant is refused even after resolving the zone
name, at the target's relay on the SYN and by its own leg on the way
out. The resolution proof asserts the negative the pin table owes:
`admits_flow` on the answer's lease is false, where a declared name's
answer keeps its window.

The denied caller's direct connect is the NET-070 e2e pair's dropped
SYN, so the KVM lane's red assertion (curl 28) now has a daemon that
refuses it.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 7 of 10 on a /revise note, pushed as 06fc697f35ee.

line exit tail
cargo nextest run -p sessions box_zone_answers_carved_out_of_infrastructure_deny 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-587c... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/189f4/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/189f4/tree/crates/ot) C
cargo nextest run -p minimald box_zone_resolution_needs_no_allow_entry 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-c314... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/802aa/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/802aa/tree/crates/ot) C
cargo nextest run -p minimald box_zone_connection_enforced_at_connect 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-64e2... Compiling stdlib v0.0.21 (/home/runner/.local/state/minimal/sessions/99869/tree/crates/stdlib) Compiling version v0.7.0 (/home/runner/.local/state/minimal/sessions/99869/tree/crate

Verify min task run agent-verify passed.

Review threads the coder judged:

  • 👍 valid — crates/minimald/src/net/switch.rs: Dropped the admits_flow pin lift in refuse_zone_connect: the source half is now exactly direct_connection_verdict == Admit, decided by allow_subnets/deny_subnets alone, with the pin remaining only the source's own relay's lift for its allow_dns_hosts names.
  • 👍 valid — crates/minimald/src/net/switch.rs:2511: Rewrote the connect proof as the two cases: a caller whose allow_subnets names the target's lease reaches it on both legs, and a caller with no CIDR grant is refused on the SYN even after resolving the zone name — the connect-time refusal under the source's label, plus the source's own leg refusing
  • 👍 valid — crates/minimald/src/net/dns_gate.rs:392: Zone answers are now validated by the rebinding intersection (each refusal still logged) but never entered in the pin table; box_zone_resolution_needs_no_allow_entry keeps the resolution assertion and adds the negative — admits_flow on the answer's lease is false — beside the contrast that a declare

A box still resolves a sibling's zone name with no allowlist entry, but the answer pins nothing: a connection to the sibling now succeeds only when the source's own allow_subnets/deny_subnets admit it beside the target's ingress rules, so the denied caller's direct connect is a dropped SYN — the KVM lane's curl 28 — while the connect-time debug line still names both boxes and each side's verdict.

Notes from the agent: The KVM lane cannot run here (no /dev/kvm, no built min/minvmd), so the NET-070 pair-5 fix is deferred to that lane (ci-linux-kvm / test-kvm): it follows from the daemon change — the pair's caller has --allow-subnets 100.64.255.253/32 and no allow_dns_hosts, so its zone resolution now creates no pin and its own egress leg drops the SYN to the target's lease (curl 28), while the proxied leg keeps the proxy's CIDR-only 403. Both halves are asserted at unit level in the two rewritten tests. Other commands run, all exit 0: cargo nextest run -p minimald --locked --no-tests=fail (496 passed, 8 skipped as env-gated; nextest flags one test LEAK — nsenter::tests::resolves_the_grandchild_not_the_forking_process, a module this round does not touch), cargo clippy -p sessions -p minimald --all-targets --locked -- -D warnings, scripts/clippy-strict.sh origin/main (clean, no hits to report). Design ruling implemented as directed, stated here for the re-read: a zone answer is validated by the rebinding intersection and never enters the pin table even when the box also declares the name in allow_dns_hosts, because reach to a sibling is CIDR-only on every surface (the source's own leg, the target's connect-time gate, the hostname proxy's caller check), so a pin for a sibling lease could only admit a frame another gate then drops. dns.rs and policy.rs needed no change this round; no file outside the task's layers was touched.

@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/net/switch.rs Resolved
Comment thread crates/minimald/src/net/switch.rs Resolved
The connect-time source refusal of refuse_zone_connect warned through
the target relay's per-box PolicyWarnLimiter under the source's label,
so it was not rate-limited against the source's own egress-leg warns for
the same (box, rule): one refusal could say two lines in a minute, and
the method doc's once-per-source-box-and-rule claim did not hold.

Emit through the source's own limiter instead — the same one its egress
leg rate-limits by — so one refusal is one R2.7 line whichever leg saw
the frame, and the doc's claim is true. The connect-time half itself
stays: it is the conjunction's backstop at the target, and its debug
line is the one place both boxes' verdicts are named.
@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 30, 2026
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 8 of 10 on the runtime's review findings, pushed as 94f9689ee0d8.

line exit tail
cargo nextest run -p sessions box_zone_answers_carved_out_of_infrastructure_deny 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-1e0d... Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/8d7ac/tree/crates/args) Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/8d7ac/tree/crates/ot) C
cargo nextest run -p minimald box_zone_resolution_needs_no_allow_entry 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-1e3b... Compiling ot v0.0.1 (/home/runner/.local/state/minimal/sessions/eaf43/tree/crates/ot) Compiling args v0.0.1 (/home/runner/.local/state/minimal/sessions/eaf43/tree/crates/args) C
cargo nextest run -p minimald box_zone_connection_enforced_at_connect 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-4d25... Compiling stdlib v0.0.21 (/home/runner/.local/state/minimal/sessions/71b41/tree/crates/stdlib) Compiling version v0.7.0 (/home/runner/.local/state/minimal/sessions/71b41/tree/crate

Verify min task run agent-verify passed.

Review threads the coder judged:

  • 👍 valid — PRRT_kwDOPdfcxM6nfjci: Held and was fixed at 06fc697; verified at the new head: source_pass is exactly direct_connection_verdict == Admit on the source's allow_subnets/deny_subnets, no admits_flow lift, and the proof passes. (not posted on the thread: not an open thread on the pull request)
  • 👍 valid — PRRT_kwDOPdfcxM6nfjco: The proof is the two requested cases at 06fc697 and passes at the new head: the caller whose allow_subnets names the target's lease reaches the published port on both legs; the caller with no CIDR grant is refused on the SYN after resolving the zone name, the R2.7 line said under the source's label (not posted on the thread: not an open thread on the pull request)
  • 👍 valid — PRRT_kwDOPdfcxM6nfjcu: Fixed at 06fc697 and verified at head: a zone name is carved out of the allow-list for reply processing, its answer is validated by the rebinding intersection, and it never enters the pin table — the proof asserts resolution needs no entry, admits_flow on the answer's lease is false, and the declar (not posted on the thread: not an open thread on the pull request)
  • 👍 valid — PRRT_kwDOPdfcxM6ni2kd: The gap was real: nothing above 9ce6a66 had been built. Built the head cold and ran every test line — all three proofs pass (exit 0), plus cargo fmt --check, cargo clippy -p minimald --all-targets with no warnings, and a clean clippy-strict over the changed crates, so the hunk's behaviour is now ob (not posted on the thread: not an open thread on the pull request)
  • 👍 valid — PRRT_kwDOPdfcxM6ni2km: The log-contract half held: the connect-time refusal warned through the target's limiter, so it was not rate-limited against the source's own egress-leg warns and the doc's once-per-source-box-and-rule claim did not hold across the two emits. Fixed in 94f9689: the refusal now warns through the sour (not posted on the thread: not an open thread on the pull request)

At this branch's head a box resolves its sibling's zone name with no egress.allow_dns_hosts entry — the answer validated by the rebinding intersection and pinned nowhere — and the connection that follows completes only when the target's ingress declaration and the source's declared subnets both admit it: the caller whose allow_subnets names the sibling's lease reaches the published port, while the caller with no CIDR grant is refused on the SYN at the target's relay and by its own egress leg, one refusal saying one R2.7 line per source box and rule through the source's own limiter, with one debug line per box-zone connection naming both boxes and each side's verdict.

Notes from the agent: Round 8. Thread 4 was the round's work: the head (06fc697) had never been built or tested, so I built it cold and ran every test line — all three proofs passed at that head unmodified, and pass again at 94f9689; cargo fmt --check on minimald, cargo clippy -p minimald --all-targets (no warnings) and ./scripts/clippy-strict.sh (scope -p minimald -p sessions, clean) also ran, so the one gate the finding names is green at the new head. Threads 1–3 were already addressed at 06fc697; I verified each against the code (source_pass is exactly direct_connection_verdict == Admit with no pin lift; the proof is the two requested cases; a zone answer is validated, never pinned, and the negative is asserted beside the declared-name contrast) and changed nothing further for them. Thread 5's log-contract half held as stated — the connect-time source refusal warned through the target's per-box limiter, so it was not rate-limited against the source's own egress-leg warns and the method doc's once-per-source-box-and-rule claim did not hold across the two emits. Fixed in 94f9689 by emitting through the source's own limiter (the same one its egress leg rate-limits by), asserted in the proof: the leg's refusal of the same frame adds no second line. The connect-time half itself stays,…[truncated]

@norrietaylor norrietaylor left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 8 at 94f9689 reviewed: the connect-time refusal now warns through the source's own limiter so the two legs dedupe to one line (R2.7); no verdict change. Round 7 fixed the blocking item at the root (zone answers validated, never pinned; source half is the declared rules alone), ARCH-EXPERT PASS on NET-072/NET-073 (§7.1 l249, §5.3 l145, UC6), KVM green on 06fc697 with the NET-070 pair-5 oracle, every thread resolved. Merge follows the checks on this head.

@norrietaylor
norrietaylor merged commit 15a81cc into main Sep 30, 2026
34 checks passed
@norrietaylor
norrietaylor deleted the agent/1647-resolve-and-reach-sibling-boxes-by-name-under-bo branch September 30, 2026 14:43
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.

T31 — Resolve and reach sibling boxes by name under both boxes' rules

1 participant

0