Repository navigation
feat(minimald): proxy runtime-published ports like a direct connection (T99) - #2714
norrietaylor merged 6 commits into
Conversation
The hostname proxy now reads the target box's runtime-published ports (an allowed expose, a published listener) from its live relay gate when the name resolves. The proxy verdict then admits exactly the ports a direct connection reaches. Admitted requests log their route source, and refusals record route_source=none.
expose_allow_publishes_and_lists now registers the box's relay gate and lease, then shows the port refused through the proxy before the expose and reachable through it right after.
…me-proxy-serves-runtime-published-port
|
Important Review skippedBot user detected. To trigger a single review, invoke the
|
|
Approved: every review thread resolved at @norrietaylor resolved the last thread, which releases the approval this review had withheld. Nothing to do. |
…me-proxy-serves-runtime-published-port
The daemon's default filter is info, so a debug line never reached the log a min bug bundle carries.
There was a problem hiding this comment.
arch-review: PASS · the full ruling
The change conforms to the parity rule in §7.1 line 249. The proxy reads the runtime ports from the box's live relay gate, which is the gate a direct connection meets. A port that is not declared and not admitted at runtime is refused with the direct connection's refusal. A box with no gate gets no runtime ports, so the change fails closed. The host_ip lane stays unfiltered, as line 249 says. The task text says 502 for an upstream timeout, but the code answers 504. The design does not name a status for this, so correct the plan text, not the code.
Ruled at 221770f against gominimal/arch@109541a48c31 by claude-opus-5-5.
|
Round 1 of 10 on the runtime's review findings, pushed as
Verify Review threads the coder judged:
A port published inside a box with Notes from the agent: Revise round 1: raised the per-request 'forwarding a proxied request' line from debug to info, because the daemon's default EnvFilter is info (crates/minimald/src/logging.rs:75). No other change. |
There was a problem hiding this comment.
minimal-review — approved
Verdict APPROVE — every review thread is resolved; @norrietaylor closed the last one. The review of 221770f9e396 withheld this approval on the threads open then. Posted as a comment: the App cannot approve its own pull request; the check and the label carry it.
There was a problem hiding this comment.
Babysitter review at 221770f9. I read the diff against the T99 plan block (NET-044, NET-069, NET-071). The route keeps no second copy of the runtime ports: HostnameRegistry::resolve reads them from the box's live relay gate (BoxGate::runtime_published_tcp, the listen-published set plus the exposed set), and Route::upstream and the new Route::source read that set. So the proxy and a direct connection read one set, and a publish or withdrawal cannot leave them apart. That covers the dns.rs and listeners.rs layer notes with no listener change, as the PR body explains. Route::source returns Declared, Runtime or Ungated, a port in neither set is refused with the direct connection's 403, and a host-address (Ungated) target forwards its bound port. The admitted-request line in handle_connection_io is now info! with route_source, which meets the observability line. All five plan test lines are in the Tests table with exit 0, and proxy_forwards_runtime_published_port is new. Arch PASS at this head, no open threads. No findings.
Generated by Claude Code
There was a problem hiding this comment.
Babysitter approval at 221770f: every merge gate passes at this head (arch PASS at 221770f, zero open threads, every lane aggregator green). Squash title feat(minimald): proxy runtime-published ports like a direct connection (T99) and the squashed message pass commitlint. Queued for squash merge.
Generated by Claude Code
… (T99) (#2714) * feat(minimald): forward runtime-published ports through the proxy The hostname proxy now reads the target box's runtime-published ports (an allowed expose, a published listener) from its live relay gate when the name resolves. The proxy verdict then admits exactly the ports a direct connection reaches. Admitted requests log their route source, and refusals record route_source=none. * test(minimald): prove an allowed expose opens through the proxy expose_allow_publishes_and_lists now registers the box's relay gate and lease, then shows the port refused through the proxy before the expose and reachable through it right after. * style(minimald): format the proxy and expose tests * fix(minimald): log admitted proxy requests at info The daemon's default filter is info, so a debug line never reached the log a min bug bundle carries. --------- Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com> (cherry picked from commit 7097b96)
The cherry-picked change names main's types and helpers. Release/0.7.0 predates the box renames and the traced spawn wrapper, so its tests and one doc link use the release's own names: - BoxGate is SessionGate, BoxPolicy is SessionPolicy, and boxes::DynamicIngress is sessions::DynamicIngress. - crate::traced does not exist here, so the new tests spawn with tokio::spawn, as the release's other proxy tests do. - The release's spawn_backend_on takes a full address and returns nothing, so proxy_forwards_runtime_published_port picks the backend port with a probe listener, the pattern the release's tests use. - The proptest's no-wider-reach check uses the outer gate, which now carries the runtime publishes, and asks admits_tcp. On this branch admits_direct_tcp covers the declared ports only, while admits_tcp also covers the listen-published and exposed ports. That is the set main's admits_tcp covers. The behaviour is #2714's: runtime-published ports join the proxy's route set, and the proxy's verdict equals the direct connection's.
…#2714) T99. The hostname proxy reads the target box's runtime-published ports (an allowed expose, a published listener) from its live relay gate when the name resolves, so its verdict admits exactly the ports a direct connection reaches. Admitted requests log their route source at info, and refusals record route_source=none. Backport of #2714, folded from #2724. Adapted to the line: - BoxGate, BoxPolicy and boxes::DynamicIngress become SessionGate, sessions::SessionPolicy and sessions::DynamicIngress. - crate::traced::spawn becomes tokio::spawn in the new tests. - The line's spawn_backend_on takes a full address, so proxy_forwards_runtime_published_port picks its port with a probe listener. - The parity proptest checks the gate that carries the runtime publishes and asks admits_tcp, since admits_direct_tcp covers only the declared ports here. Also keeps proxy_refuses_a_withdrawn_port_like_direct (NET-069, from #2732): it publishes and withdraws through the gate only, because the line's listen watcher has no record_dynamic_hold. (cherry picked from commit 7097b96)
T99 — The hostname proxy serves runtime-published ports like a direct connection
slice: S0
kind: task
covers: NET-044, NET-069, NET-071
size: L
est: 320 code, 400 tests
executor: agent
difficulty: needs care
model: standard
diagnostics:
min bugbundles carry the daemon log, where each proxied refusal records the target box, the port and whether the port was declared, runtime-published or neitherobservability: the proxy's per-request log line gains the route source (
declared,runtime) for an admitted request; a refusal keeps its rate-limited warn linelayers:
- routes:
- crates/minimald/src/net/dns.rs:
record_dynamic_holdand the listen-publish path add the port to the box's proxy route (own_route,Route::upstream) as well as toown_holders, and the withdrawal removes it, so the proxy's published set is the direct connection's- crates/minimald/src/net/listeners.rs: a runtime publish and its withdrawal update the route through the registry in the same step the forward is bound or unbound
- crates/minimald/src/box_actor/tests.rs: an
allow-decided expose lists the mapping inmin session policyand is reachable through the proxy at oncetest: cargo nextest run -p minimald expose_allow_publishes_and_lists
test: cargo nextest run -p minimald expose_deny_typed_error
- proxy:
- crates/minimald/src/net/proxy.rs: the proxy's verdict equals the direct connection's in every network mode, and the status mapping stays as it is: a refused verdict reads 403 naming the reason, an admitted request whose upstream refuses or times out reads 502; a runtime-published port is forwarded; a host-address target's bound port is forwarded even when undeclared, since that lane does not filter
- crates/minimald/src/net/switch.rs: the refusal path reads the published set, declared and runtime, rather than the declared ingress alone
test: cargo nextest run -p minimald proxy_undeclared_port_refused_like_direct
test: cargo nextest run -p minimald proxy_parity_across_network_modes
test: cargo nextest run -p minimald proxy_forwards_runtime_published_port
produces:
- crates/minimald/src/net/dns.rs
consumes: none
proof: A port published inside a box with
min net exposeor by starting a listener opens athttp://<name>.min.internal:7654as it does directly, and a port a direct connection cannot reach is not reached through the proxy either, in every network mode.Proof
A port that a box publishes at runtime, with
min net exposedecided allow or by a listener, now opens athttp://<name>.min.internal:7654from the moment the box's relay gate admits it, the same as a direct connection. The proxy refuses the port with the direct connection's 403 refusal before the publish and after the withdrawal. A port that no declaration and no runtime publish names stays refused in every network mode. expose_allow_publishes_and_lists shows a real request through the proxy that reaches an exposed port, and proxy_parity_across_network_modes holds the proxy's verdict to the gate's verdict for runtime ports in all three network modes.Notes from the agent: Design: the route does not keep a second copy of the runtime ports. HostnameRegistry::resolve reads them from the box's live relay gate (new BoxGate::runtime_published_tcp, which is the listen-published set plus the exposed set), keyed by the lease the attach reported. Route carries them in a new
runtimeset, and Route::upstream and the new Route::source read it. The proxy and a direct connection therefore read the same set, and no lifecycle step can let the two drift. Because of this, two layer notes needed no change. (1) dns.rsrecord_dynamic_holdand the withdrawal do not add or remove route ports: the gate admission that the listen watcher already makes is what the route reads. (2) listeners.rs is not changed. The expose path in box_actor/ports.rs commits its publication before the audit record and the gate admission. A route update hooked on BoxPublications (the only listeners.rs hook the expose path reaches) would have forwarded an unaudited expose that the gate refuses, so I did not use one. Status mapping: I did not change it. The task says an upstream timeout reads 502, but the code before this change answers 504 Gateway Timeout for a dial timeout, and that is still so. A refused upstream reads 502. Observability: admitted requests log a debug line 'forwarding a proxied…[truncated]Tests
cargo nextest run -p minimald expose_allow_publishes_and_listscargo nextest run -p minimald expose_deny_typed_errorcargo nextest run -p minimald proxy_undeclared_port_refused_like_directcargo nextest run -p minimald proxy_parity_across_network_modescargo nextest run -p minimald proxy_forwards_runtime_published_portVerify
min task run agent-verifypassed ([tasks.verify]).Base
The base branch moved while the coder worked; it was merged in before the tests ran.
Closes #2688