--- model: claude-opus-5 service: claude timestamp: 2026-08-13T00:11:02Z git_ref: 27c34aeb diff_cmd: git diff main..ng_tpts_planning --- # Raw output — next-gen tpt-backend implementation plans ## Generated planning docs > `git diff main..ng_tpts_planning -- ai/tpt-backends/` Five markdown docs. `00_shared_backend_contract.md` is normative and the other three are written against it so they can be worked independently: - **`00_shared_backend_contract.md`** — the backend duck-type (`Address(msgspec.Struct, frozen=True)` + module-level `start_listener()`/`close_listener()` + a `MsgpackStream(MsgpackTransport)`), the `inspect.getmodule(self.addr)` reflection in `Endpoint.start_listener()` that forces the Address class and its listener fns to share a module, a 10-item registration checklist, the dep policy, the test-harness shape, and §1.1's proto-key conclusion (below). - **`01_tipc_backend.md`** — service addressing via `TIPC_ADDR_NAMESEQ` (bind/publish) and `TIPC_ADDR_NAME` (connect/lookup), `TIPC_TOP_SRV` topology subscriptions as a push-based registry, the `get_random()` instance-collision hazard, and a step-0 capability-probe spike. - **`02_quic_iroh_backend.md`** — `iroh` over `aioquic`/`quiche`/`trio-asyncio`, a `_uniffi_trio.py` bridge built on `TrioToken.run_sync_soon()`, `trio.abc.Listener`/ `HalfCloseableStream` adapters, and an API-truth table to fill in during step 0. - **`03_wg_tunnel_bindspace.md`** — `wg` as a *bindspace* rather than a `MsgTransport`, a `TunnelledAddress` wrapper delegating `.proto_key`/`.unwrap()` to `.inner`, `pyroute2` for layer B, and `@acm`-managed netns/iface for layer C. - **`README.md`** — index. ## Generated example code > `git diff main..ng_tpts_planning -- examples/multihost/wg_lan/` - `wg_maddr.py` — `WGTunnelledAddr(msgspec.Struct, frozen=True)` carrying `bearer: tuple[str, int]`, `peer_pubkey: str`, `inner: tuple[str, int]`, `inner_proto: Literal['tcp']`, plus a `.maddr` property that re-renders the canonical form. Pure helpers `mb_pubkey()`, `wg8_pubkey()`, `parse_wg_maddr()`, and `_segments()` (with a marked stopgap for when the `wg` codec isn't installed). `verify_wg_peer()` is impure **by design** and kept out of the parse path. - `host_a_srv.py` / `host_b_client.py` — the two-host runs; both pass only `addr.inner` to `open_nursery()`/`open_root_actor()`. - `README.md` — grammar, owner table, `#108`-branch install line, tunnel setup, "what changed vs #482". ## Verified findings (non-code, verbatim) ### `trio` is address-family agnostic Read against the installed `trio`. `SocketStream`/`SocketListener` ctor checks are only "is a trio sock object" + `type == SOCK_STREAM`, plus an `OSError`-**suppressed** `SO_ACCEPTCONN` probe. No `AF_*` check anywhere; `TCP_NODELAY`/`TCP_NOTSENT_LOWAT` are set under `suppress(OSError)`. A TIPC `SOCK_STREAM` sock should therefore drop straight into `trio.serve_listeners()` with the existing `MsgpackTransport` framing, making TIPC mostly table-registration boilerplate w/ zero new deps. ### the `wg` maddr grammar — `/wg/` is infix, not suffix Installed `baudco/py-multiaddr@wg_support` (PR multiformats/py-multiaddr#108) into a throwaway venv and round-tripped every candidate form: | maddr | `[p.name for p in m.protocols()]` | | --- | --- | | `/ip4/1.2.3.4/udp/51820/wg/u` | `['ip4','udp','wg']` | | `/ip4/../udp/../wg/u/ip4/../tcp/..` | `['ip4','udp','wg','ip4','tcp']` | | `/ip4/10.0.11.1/tcp/1616/wg/u` | `['ip4','tcp','wg']` | ``` /ip4/192.168.1.50/udp/51820/wg/u/ip4/10.0.11.1/tcp/1616 \_______ bearer __________/\__ key __/\______ overlay ______/ ``` Segments *before* `/wg/` are the bearer — the underlay `(ip, udp-port)` that `wg(8)` itself listens on (`ListenPort`). Segments *after* are the overlay endpoint, the only part tractor binds. The third row above is #482's original suffix form: it parses, but is semantically inverted. Three parts, three owners — and only one is an `Endpoint`: | part | bound by | in the runtime? | | --- | --- | --- | | bearer | kernel, via `wg-quick`/`pyroute2` | no | | `/wg/u` | nothing — an identity | no, verified out-of-band | | overlay | `tractor`'s `IPCServer` | yes, as `.inner` | ### proto-key-tagged `UnwrappedAddress` Shape-matching in `wrap_address()` does not survive four backends. TIPC's natural unwrapped form is a `(str, int)`, indistinguishable from `TCPAddress`; iroh's is a `(str, str)`, already swallowed by the existing UDS case (`case (_, filename) if type(filename) is str`). Ordering hacks and prefix-tagging only paper over it. Recommended prerequisite for all three backends: carry an explicit proto-key spelled with the `multiaddr` protocol name — `('tcp', host, port)`, `('unix', path)`, `('tipc', stype, inst, scope)` — so `wrap_address()` collapses to `_address_types[addr[0]]` and the collision class stops existing. This also makes the on-wire form agree with `mk_maddr()`/`parse_maddr()` instead of being an independent invention. It is a wire-format change (`SpawnSpec`, `_root_mailbox`, `_registry_addrs`) plus every fixture and downstream config, so it wants its own migration commit landed before any new backend — and it is the moment to stop handing raw tuples to users at all, making `Address` the public currency and `UnwrappedAddress` an internal serialization detail (the discipline `ipaddress` uses). ### netns is a runtime-level config API `setns(2)` affects the calling thread only and does not move already-created sockets. So a netns is a spawn/boot-time input alongside `enable_transports`/`tpt_bind_addrs`, and there is deliberately no `await actor.enter_netns(...)` — a mid-life API would silently leave the IPC server bound in the old namespace. Corollary for layer B: pass `netns=` down to `pyroute2` rather than assuming a `trio.to_thread` worker inherits it. ### `examples/` collection would have failed CI `tests/test_docs_examples.py` walks `examples/` recursively and subproc-runs every collected file asserting `rc == 0`. Its filter never checks the extension, so all four `wg_lan` files were collected — including `README.md`, which would have been run as `python README.md`. `'multihost' not in p[0]` was already in the exclusion list with no directory using it. Moving the set under `examples/multihost/wg_lan/` drops collection 24 → 20 with zero test changes; confirmed via `pytest --collect-only`. ## Corrections applied during the session The human corrected two claims that had been asserted without verification, both since retracted in-place in the docs and in the posted issue comments: 1. that `wg` has "nothing to bind at the tunnel layer, exactly one bind" — wrong; a wg stack is genuinely composed, and the real axis is *who owns* each layer's endpoint. 2. that `/ip4/../udp/443/quic-v1` was "also composed" — wrong; that is one endpoint with a protocol qualifier, not a tunnel. A self-review before publication also downgraded two overconfident claims to explicitly-flagged assumptions: the `uniffi`-uses-asyncio-only-as-executor thesis (contradicted that plan's own "do not guess from memory" step 0) and TIPC's duplicate-binder round-robin behaviour (unverified).