Peel tunnels before `Endpoint` binding
Carry tunnel declarations through listener configuration, then strip them immediately before constructing transport endpoints. Also allocate random listener addresses from a contacted registry's overlay, and prove a real TCP listener never stores the wrapper while the source declaration retains its bindspace metadata. Prompt-IO: ai/prompt-io/opencode/20260819T213145Z_f81fc5e5_prompt_io.md (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))wkt/addr_unpacking
parent
d0a9593aff
commit
dfad66a00a
|
|
@ -0,0 +1,40 @@
|
||||||
|
---
|
||||||
|
model: gpt-5.6-sol
|
||||||
|
service: opencode
|
||||||
|
session: tractor-addr-unpacking-followup
|
||||||
|
timestamp: 2026-08-19T21:31:45Z
|
||||||
|
git_ref: f81fc5e5
|
||||||
|
scope: code
|
||||||
|
substantive: true
|
||||||
|
raw_file: 20260819T213145Z_f81fc5e5_prompt_io.raw.md
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prompt
|
||||||
|
|
||||||
|
The human requested completion of inbound runtime peeling using the
|
||||||
|
same per-step implementation and commit-plan workflow.
|
||||||
|
|
||||||
|
## Response summary
|
||||||
|
|
||||||
|
Preserved tunnel declarations through listener configuration, peeled
|
||||||
|
them immediately before `Endpoint` construction, and used the overlay
|
||||||
|
for backend-specific random listener allocation after registry
|
||||||
|
discovery. Added a real listener regression for the reflection and
|
||||||
|
exact-type boundary.
|
||||||
|
|
||||||
|
## Files changed
|
||||||
|
|
||||||
|
- `tractor/ipc/_server.py` - accept wrapper declarations and peel at
|
||||||
|
`Endpoint` construction.
|
||||||
|
- `tractor/_root.py` - allocate random transport addresses from the
|
||||||
|
contacted registry's overlay.
|
||||||
|
- `tests/ipc/test_server_tunnel_boundary.py` - verify a real listener
|
||||||
|
stores only TCP while preserving the source annotation.
|
||||||
|
|
||||||
|
## Human edits
|
||||||
|
|
||||||
|
The human selected the runtime-boundary work and previously corrected
|
||||||
|
the architecture so tractor retains future bindspace provisioning
|
||||||
|
ownership while `Endpoint` sees only application transports. The agent
|
||||||
|
implemented and tested that direction; no direct manual source edits
|
||||||
|
were observed during this step.
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
model: gpt-5.6-sol
|
||||||
|
service: opencode
|
||||||
|
timestamp: 2026-08-19T21:31:45Z
|
||||||
|
git_ref: f81fc5e5
|
||||||
|
diff_cmd: git diff HEAD~1..HEAD
|
||||||
|
---
|
||||||
|
|
||||||
|
# Raw output - inbound tunnel boundary
|
||||||
|
|
||||||
|
The human requested runtime boundary integration while preserving the
|
||||||
|
future tractor-owned bindspace lifecycle.
|
||||||
|
|
||||||
|
> `git diff HEAD~1..HEAD -- tractor/ipc/_server.py tractor/_root.py tests/ipc/test_server_tunnel_boundary.py`
|
||||||
|
|
||||||
|
Broadened listener declarations to carry tunnel wrappers until
|
||||||
|
`_serve_ipc_eps()` and peeled immediately before `Endpoint`
|
||||||
|
construction. Also peeled a contacted tunnelled registry before
|
||||||
|
backend-specific random listener allocation. Added a real TCP listener
|
||||||
|
regression proving `Endpoint` stores only the resolved overlay while
|
||||||
|
the original declaration retains bindspace metadata.
|
||||||
|
|
||||||
|
Verification included `465` collected tests, `84` passing
|
||||||
|
discovery/IPC tests with two xpasses, Ruff, and the full suite with
|
||||||
|
`447` passes.
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
'''
|
||||||
|
Tunnel annotation peeling at the inbound IPC transport boundary.
|
||||||
|
|
||||||
|
'''
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import trio
|
||||||
|
|
||||||
|
from tractor.discovery import (
|
||||||
|
TunnelledAddress,
|
||||||
|
WGTunnelSpec,
|
||||||
|
tunnels_of,
|
||||||
|
)
|
||||||
|
from tractor.ipc._server import open_ipc_server
|
||||||
|
from tractor.ipc._tcp import TCPAddress
|
||||||
|
|
||||||
|
|
||||||
|
_PUBKEY: str = 'g3x7z0AdV1rM6UQU22CC7IL3/ivn4DzrE7ikDhCZ/Dc='
|
||||||
|
|
||||||
|
|
||||||
|
def test_server_peels_before_endpoint_construction():
|
||||||
|
'''
|
||||||
|
`Endpoint.start_listener()` reflects on its address's declaring
|
||||||
|
module, so retaining a tunnel wrapper there selects `._tunnel`
|
||||||
|
instead of the TCP backend. Start a real listener from the
|
||||||
|
wrapper, assert the resulting `Endpoint` contains only a resolved
|
||||||
|
`TCPAddress`, and prove the original declaration still carries
|
||||||
|
its tunnel spec for the future bindspace lifecycle.
|
||||||
|
|
||||||
|
'''
|
||||||
|
overlay = TCPAddress('127.0.0.1', 0)
|
||||||
|
tunnelled = TunnelledAddress(
|
||||||
|
overlay=overlay,
|
||||||
|
tunnel=WGTunnelSpec(
|
||||||
|
peer_pubkey=_PUBKEY,
|
||||||
|
bearer=('192.168.1.50', 51820),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def main() -> None:
|
||||||
|
async with open_ipc_server() as server:
|
||||||
|
eps = await server.listen_on(
|
||||||
|
accept_addrs=[tunnelled],
|
||||||
|
)
|
||||||
|
assert len(eps) == 1
|
||||||
|
endpoint = eps[0]
|
||||||
|
|
||||||
|
assert type(endpoint.addr) is TCPAddress
|
||||||
|
host, port = endpoint.addr.unwrap()
|
||||||
|
assert host == overlay.unwrap()[0]
|
||||||
|
assert port > 0
|
||||||
|
assert endpoint.addr is not tunnelled
|
||||||
|
assert tunnels_of(tunnelled) == (
|
||||||
|
tunnelled.tunnel,
|
||||||
|
)
|
||||||
|
|
||||||
|
server.cancel()
|
||||||
|
|
||||||
|
trio.run(main)
|
||||||
|
|
@ -57,6 +57,7 @@ from .discovery._addr import (
|
||||||
mk_uuid,
|
mk_uuid,
|
||||||
wrap_address,
|
wrap_address,
|
||||||
)
|
)
|
||||||
|
from .discovery._tunnel import strip_tunnels
|
||||||
from .trionics import (
|
from .trionics import (
|
||||||
is_multi_cancelled,
|
is_multi_cancelled,
|
||||||
collapse_eg,
|
collapse_eg,
|
||||||
|
|
@ -534,6 +535,7 @@ async def open_root_actor(
|
||||||
# proto if not already provided.
|
# proto if not already provided.
|
||||||
if not tpt_bind_addrs:
|
if not tpt_bind_addrs:
|
||||||
for addr in ponged_addrs:
|
for addr in ponged_addrs:
|
||||||
|
bindable_addr: Address = strip_tunnels(addr)
|
||||||
tpt_bind_addrs.append(
|
tpt_bind_addrs.append(
|
||||||
# XXX, these are `Address` NOT `UnwrappedAddress`.
|
# XXX, these are `Address` NOT `UnwrappedAddress`.
|
||||||
#
|
#
|
||||||
|
|
@ -541,8 +543,8 @@ async def open_root_actor(
|
||||||
# protos we allocate port=0 such that the system
|
# protos we allocate port=0 such that the system
|
||||||
# allocates a random value at bind time; this
|
# allocates a random value at bind time; this
|
||||||
# happens in the `.ipc.*` stack's backend.
|
# happens in the `.ipc.*` stack's backend.
|
||||||
addr.get_random(
|
bindable_addr.get_random(
|
||||||
bindspace=addr.bindspace,
|
bindspace=bindable_addr.bindspace,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,13 +59,17 @@ from ..msg import (
|
||||||
from ..trionics import maybe_open_nursery
|
from ..trionics import maybe_open_nursery
|
||||||
from ..runtime import _state
|
from ..runtime import _state
|
||||||
from .. import log
|
from .. import log
|
||||||
from ..discovery._addr import Address
|
from ..discovery._addr import (
|
||||||
|
Address,
|
||||||
|
UnwrappedAddress,
|
||||||
|
)
|
||||||
from ._chan import Channel
|
from ._chan import Channel
|
||||||
from ._transport import MsgTransport
|
from ._transport import MsgTransport
|
||||||
from ._uds import UDSAddress
|
from ._uds import UDSAddress
|
||||||
from ._tcp import TCPAddress
|
from ._tcp import TCPAddress
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from ..discovery._tunnel import TunnelledAddress
|
||||||
from ..runtime._runtime import Actor
|
from ..runtime._runtime import Actor
|
||||||
from ..runtime._supervise import ActorNursery
|
from ..runtime._supervise import ActorNursery
|
||||||
|
|
||||||
|
|
@ -959,7 +963,9 @@ class Server(Struct):
|
||||||
async def listen_on(
|
async def listen_on(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
accept_addrs: list[tuple[str, int|str]]|None = None,
|
accept_addrs: list[
|
||||||
|
UnwrappedAddress|Address|TunnelledAddress
|
||||||
|
]|None = None,
|
||||||
stream_handler_nursery: Nursery|None = None,
|
stream_handler_nursery: Nursery|None = None,
|
||||||
) -> list[Endpoint]:
|
) -> list[Endpoint]:
|
||||||
'''
|
'''
|
||||||
|
|
@ -1042,7 +1048,7 @@ async def _serve_ipc_eps(
|
||||||
*,
|
*,
|
||||||
server: IPCServer,
|
server: IPCServer,
|
||||||
stream_handler_tn: Nursery,
|
stream_handler_tn: Nursery,
|
||||||
listen_addrs: list[tuple[str, int|str]],
|
listen_addrs: list[Address|TunnelledAddress],
|
||||||
|
|
||||||
task_status: TaskStatus[
|
task_status: TaskStatus[
|
||||||
Nursery,
|
Nursery,
|
||||||
|
|
@ -1058,6 +1064,8 @@ async def _serve_ipc_eps(
|
||||||
`.cancel_server()` is called.
|
`.cancel_server()` is called.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
from ..discovery._tunnel import strip_tunnels
|
||||||
|
|
||||||
try:
|
try:
|
||||||
listen_tn: Nursery
|
listen_tn: Nursery
|
||||||
async with trio.open_nursery() as listen_tn:
|
async with trio.open_nursery() as listen_tn:
|
||||||
|
|
@ -1066,7 +1074,8 @@ async def _serve_ipc_eps(
|
||||||
# XXX NOTE, required to call `serve_listeners()` below.
|
# XXX NOTE, required to call `serve_listeners()` below.
|
||||||
# ?TODO, maybe just pass `list(eps.values()` tho?
|
# ?TODO, maybe just pass `list(eps.values()` tho?
|
||||||
listeners: list[trio.abc.Listener] = []
|
listeners: list[trio.abc.Listener] = []
|
||||||
for addr in listen_addrs:
|
for declared_addr in listen_addrs:
|
||||||
|
addr: Address = strip_tunnels(declared_addr)
|
||||||
ep = Endpoint(
|
ep = Endpoint(
|
||||||
addr=addr,
|
addr=addr,
|
||||||
listen_tn=listen_tn,
|
listen_tn=listen_tn,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue