diff --git a/ai/prompt-io/opencode/20260819T213144Z_f81fc5e5_prompt_io.md b/ai/prompt-io/opencode/20260819T213144Z_f81fc5e5_prompt_io.md new file mode 100644 index 00000000..53cd9556 --- /dev/null +++ b/ai/prompt-io/opencode/20260819T213144Z_f81fc5e5_prompt_io.md @@ -0,0 +1,37 @@ +--- +model: gpt-5.6-sol +service: opencode +session: tractor-addr-unpacking-followup +timestamp: 2026-08-19T21:31:44Z +git_ref: f81fc5e5 +scope: code +substantive: true +raw_file: 20260819T213144Z_f81fc5e5_prompt_io.raw.md +--- + +## Prompt + +The human requested runtime boundary integration with the established +per-step implementation and commit-plan workflow. + +## Response summary + +Kept `TunnelledAddress` available to callers while peeling it at the +last outbound boundary before transport lookup and dialing. Added a +regression which captures both transport arguments and confirms plain +TCP behavior is unchanged. + +## Files changed + +- `tractor/ipc/_chan.py` - peel tunnel annotations before outbound + transport dispatch and connection. +- `tests/ipc/test_channel_tunnel_boundary.py` - verify plain and + tunnelled channel inputs deliver only TCP overlays. + +## Human edits + +The human chose the runtime-boundary slice, required the existing +per-step commit-plan flow, and previously established that wrappers +must retain bindspace metadata without impersonating transports. The +agent implemented those constraints; no direct manual source edits were +observed during this step. diff --git a/ai/prompt-io/opencode/20260819T213144Z_f81fc5e5_prompt_io.raw.md b/ai/prompt-io/opencode/20260819T213144Z_f81fc5e5_prompt_io.raw.md new file mode 100644 index 00000000..3a32943c --- /dev/null +++ b/ai/prompt-io/opencode/20260819T213144Z_f81fc5e5_prompt_io.raw.md @@ -0,0 +1,25 @@ +--- +model: gpt-5.6-sol +service: opencode +timestamp: 2026-08-19T21:31:44Z +git_ref: f81fc5e5 +diff_cmd: git diff HEAD~1..HEAD +--- + +# Raw output - outbound tunnel boundary + +The human requested the next tunnelled-address slice using the same +per-step commit-plan flow. Existing design decisions require retaining +tunnel metadata until the narrow IPC transport boundary and never +teaching exact-type transport tables about tunnel wrappers. + +> `git diff HEAD~1..HEAD -- tractor/ipc/_chan.py tests/ipc/test_channel_tunnel_boundary.py` + +Extended channel address inputs to accept tunnel declarations, then +called `strip_tunnels()` immediately before exact-type transport lookup +and `connect_to()`. Added plain/tunnel parameterized coverage proving +both operations receive the identical TCP overlay while the original +wrapper retains its tunnel spec. + +Verification included focused IPC tests, Ruff, discovery/IPC suites, +and the full tractor suite. diff --git a/tests/ipc/test_channel_tunnel_boundary.py b/tests/ipc/test_channel_tunnel_boundary.py new file mode 100644 index 00000000..0c04b32d --- /dev/null +++ b/tests/ipc/test_channel_tunnel_boundary.py @@ -0,0 +1,91 @@ +''' +Tunnel annotation peeling at the outbound IPC transport boundary. + +''' +from __future__ import annotations + +import pytest +import trio + +from tractor.discovery import ( + TunnelledAddress, + WGTunnelSpec, + tunnels_of, +) +from tractor.ipc import _chan +from tractor.ipc._tcp import TCPAddress + + +_PUBKEY: str = 'g3x7z0AdV1rM6UQU22CC7IL3/ivn4DzrE7ikDhCZ/Dc=' + + +@pytest.fixture +def overlay() -> TCPAddress: + return TCPAddress('127.0.0.1', 0) + + +@pytest.fixture +def tunnelled( + overlay: TCPAddress, +) -> TunnelledAddress: + return TunnelledAddress( + overlay=overlay, + tunnel=WGTunnelSpec( + peer_pubkey=_PUBKEY, + bearer=('192.168.1.50', 51820), + ), + ) + + +@pytest.mark.parametrize('use_tunnel', [False, True]) +def test_channel_peels_before_transport_dispatch( + monkeypatch, + overlay: TCPAddress, + tunnelled: TunnelledAddress, + use_tunnel: bool, +): + ''' + Exact-type transport lookup cannot dispatch a `TunnelledAddress`, + and passing one onward would make TCP dial the wrong object. Feed + both a plain overlay and its annotated wrapper into + `Channel.from_addr()`, capture lookup and connect arguments, and + prove both transport operations receive only the same bindable + TCP address while the caller's tunnel metadata remains intact. + + ''' + seen: list[tuple[str, TCPAddress]] = [] + + class FakeTransport: + @classmethod + async def connect_to( + cls, + addr: TCPAddress, + **kwargs, + ) -> FakeTransport: + seen.append(('connect', addr)) + return cls() + + def fake_transport_from_addr( + addr: TCPAddress, + ) -> type[FakeTransport]: + seen.append(('lookup', addr)) + return FakeTransport + + monkeypatch.setattr( + _chan, + 'transport_from_addr', + fake_transport_from_addr, + ) + + async def main() -> None: + declared = tunnelled if use_tunnel else overlay + chan = await _chan.Channel.from_addr(declared) + assert isinstance(chan.transport, FakeTransport) + + trio.run(main) + + assert seen == [ + ('lookup', overlay), + ('connect', overlay), + ] + assert tunnels_of(tunnelled) == (tunnelled.tunnel,) diff --git a/tractor/ipc/_chan.py b/tractor/ipc/_chan.py index a2450585..c8222383 100644 --- a/tractor/ipc/_chan.py +++ b/tractor/ipc/_chan.py @@ -45,6 +45,10 @@ from tractor.discovery._addr import ( Address, UnwrappedAddress, ) +from tractor.discovery._tunnel import ( + TunnelledAddress, + strip_tunnels, +) from tractor.log import get_logger from tractor._exceptions import ( MsgTypeError, @@ -181,16 +185,17 @@ class Channel: @classmethod async def from_addr( cls, - addr: UnwrappedAddress, + addr: UnwrappedAddress|Address|TunnelledAddress, **kwargs ) -> Channel: if not is_wrapped_addr(addr): - addr: Address = wrap_address(addr) + addr = wrap_address(addr) - transport_cls = transport_from_addr(addr) + transport_addr: Address = strip_tunnels(addr) + transport_cls = transport_from_addr(transport_addr) transport = await transport_cls.connect_to( - addr, + transport_addr, **kwargs, ) # XXX, for UDS *no!* since we recv the peer-pid and build out @@ -518,7 +523,7 @@ class Channel: @acm async def _connect_chan( - addr: UnwrappedAddress + addr: UnwrappedAddress|Address|TunnelledAddress, ) -> typing.AsyncGenerator[Channel, None]: ''' Create and connect a `Channel` to the provided `addr`, disconnect