Add impl plans for TIPC/QUIC/wg tpt backends - #492
Open
goodboy wants to merge 13 commits into
Open
Conversation
First doc of a new `ai/tpt-backends/` set: the normative description of what a `tractor` tpt backend *is* as of `main`, written so the 3 sibling plans (TIPC, QUIC, `wg`) can be worked independently (by another model/provider) w/o design drift. Deats, - the backend duck-type as empirically derived from `_tcp.py`/`_uds.py`: the `Address` protocol surface, the mod-level `start_listener()`/`close_listener()` pair and `Msgpack<Proto>Stream(MsgpackTransport)`. - the ONE reflection you can't break: `Endpoint.start_listener()` resolves the tpt mod via `inspect.getmodule(self.addr)`, so an `Address` type and its listener fns MUST live in the same mod. - a 10-item registration checklist (`_address_types`, `_key_to_transport`, `_addr_to_transport`, `wrap_address()` match-cases, `TransportProtocolKey`, maddr tables, ..) incl. the import-time `_default_lo_addrs` trap. - where the `trio.SocketListener` assumption is *actually* load-bearing (just the `getsockname()` reconcile) vs. merely annotated. - the handshake/discovery invariants a new backend inherits, dep policy (extras + import-laziness per the #470 boot-latency budget), `--tpt-proto` harness plumbing and code style. Also, records a verified finding the plans lean on hard: `trio.SocketStream`/`SocketListener` are addr-*family* agnostic — the only ctor checks are "is a trio sock" + `SOCK_STREAM` (+ an `OSError`-suppressed `SO_ACCEPTCONN`) — so any `SOCK_STREAM` family CPython can make drops into the existing `trio.serve_listeners()` path unmodified. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
Plan doc for gh #378, the cheapest new backend we can add: it's stdlib-only (CPython ships `AF_TIPC` + 23 `TIPC_*` consts) and per the contract doc `trio`'s stream/listener wrappers don't care about the addr family, so `MsgpackTransport` framing and `trio.serve_listeners()` are reused verbatim. Deats, - `TIPCAddress` as a *service name* `(type, instance)` w/ scope as the `.bindspace`; `bind()` publishes the singleton name-range, peers `connect()` by name and the kernel resolves + load-balances. I.e. registration/lookup for free, no registrar in the loop. - the self-tagging `('tipc:<stype>:<scope>', instance)` unwrapped form + why it must be match-ordered before `TCPAddress`'s. - `get_random()` via a blake2b digest of the actor id (there's no `port=0` analogue) and the silent-crosstalk risk that follows: TIPC *allows* dup binders and round-robins, so a collision doesn't `EADDRINUSE`, it cross-talks. - an `Address.rebind_from_sockname` ClassVar to opt out of `Endpoint.start_listener()`'s `getsockname()` reconcile, which for TIPC always returns a port-id, never the bound name. - the `TIPC_TOP_SRV` topology-service subscription as an `@acm` yielding a chan of typed name-table events — push-based register/dereg, the real "end game cluster proto" bit. - commit sequencing, hard capability gating (`modprobe tipc`; bare `AF_TIPC` is `EAFNOSUPPORT` on a stock box), CI matrix notes, risks + follow-up seeds. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
Plan doc for gh #353. Picks `iroh` (the `uniffi` FFI pkg) over `aioquic`/`quiche` bc node-id addressing + hole-punching + relay fallback is the whole point; `aioquic` stays documented as the fallback since ~90% of the adapters here are reusable against a sans-io core. Deats, - the layering: iroh `Endpoint` per actor, `Connection` per peer (pooled via `trionics.maybe_open_context()`, not a hand-rolled cache), one bi-stream per `Channel`. 4-byte prefix framing stays so `MsgpackTransport` is untouched. - `_uniffi_trio.py`: uniffi only uses `asyncio` as the executor for its rust-future poll loop, so a ~40-line `TrioToken.run_sync_soon()` bridge replaces it. Spells out the real hazards — strong ref on the `ctypes` trampoline, poll-code propagation, and a *bounded* shielded cancel-drain so a wedged rust future can't make an actor un-cancellable. - `IrohAddress` w/ ALPN as the `.bindspace`, the `(str, str)` unwrapped form's collision w/ the UDS match-case, and why `get_root()` needs a persisted secret key -> a lazy `default_lo_addrs()` + a pure-getter/explicit-setter split. - `QuicMsgStream(trio.abc.HalfCloseableStream)` + `QuicListener(trio.abc.Listener)`, incl. the exact EOF/reset/use-after-close semantics `_transport.py` already match-cases on, and hanging the acceptor tasks off the existing `Endpoint.listen_tn`. - a prep-PR boundary: annotation widening, the shared `rebind_from_sockname` gate and a `tpt_key`-based `transport_from_stream()` dispatch, all landable w/ tcp/uds as the only backends. Further, notes this is our first tpt w/ real transport security + peer auth, so an inbound node-id allowlist hook belongs here — and that it says nothing about the other backends. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
Plan doc for gh #482 + the tunnelled-maddr item of #443. Pushes back on the framing that `wg` is a tpt: it's transparent to `socket(2)`, so it belongs as a *bindspace* — a scoped `@acm`-managed net ctx that an existing L4 tpt binds *inside* — and it's what finally implements the long-spec'd (never implemented) `Address.namespace`. Deats, 3 independently-shippable layers, - A) declarative: commit #482's examples, teach `parse_maddr()` the `/…/wg/u<key>` suffix -> a `TunnelledAddress` wrapper whose `.proto_key`/`.unwrap()` delegate to `.inner` so nothing new crosses the wire and every existing table lookup keeps working. - B) swap the `subprocess.run(['sudo', 'wg', 'show'])` shelling for `pyroute2`. Default to `trio.to_thread` around the sync API (these are one-shot ops at bind/teardown, never hot-path), w/ sans-io codecs + a trio `AF_NETLINK` sock as the follow-up for the read paths. Explicitly forbids dragging `trio-asyncio` in. - C) `open_bindspace()`/`open_netns()`/`open_wg_iface()` `@acm`s folded w/ an `AsyncExitStack`, + filling in the `# !TODO, always be ns aware!` placeholder already sitting in `Endpoint.pformat()`. Also flags the subtlest bug in the whole thing: `setns(2)` is *per-thread*, so a `pyroute2` query issued via `trio.to_thread` lands in the *original* netns. Test-first, per usual. Further, designs for the generalization (`TunnelSpec` union + `match` dispatch) while only implementing `wg`+netns, and calls out `veth`-in-netns as the better *first* one bc it makes a fully self-contained two-"host" integration test possible w/o `wg` at all. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
Landing page for `ai/tpt-backends/`: points at the contract spec as required first reading, tables the 3 plans against their issues/deps/size, and states the landing order + why. Deats, - TIPC first as the cheap proof the table-registration story generalizes to a genuinely new proto (stdlib-only, and `trio`'s sock wrappers are family-agnostic). - `wg` layer-A next since it's deployable-today doc/example work. - QUIC last, gated on its own prep PR. - notes that plans 01 and 02 both want the same `Address.rebind_from_sockname` gate, so whichever lands first ships it. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
Shape-matching in `wrap_address()` doesn't survive 4 backends and
the plans were papering over it: TIPC's natural unwrapped form is
a `(str, int)`, indistinguishable from `TCPAddress`, and iroh's
is a `(str, str)`, which the *existing* UDS case
(`case (_, filename) if type(filename) is str`) already swallows.
So the contract doc (§1.1) now carries the conclusion as a
**recommended prerequisite for all three backends**: make the
unwrapped form carry an explicit proto-key spelled with the
`multiaddr` protocol name — `('tcp', host, port)`,
`('unix', path)`, `('tipc', stype, inst, scope)`. `wrap_address()`
then collapses from an order-sensitive `match` to
`_address_types[addr[0]]` and the whole collision class stops
existing, while the on-wire form finally agrees w/
`mk_maddr()`/`parse_maddr()` instead of being an independent
invention.
Two consequences spelled out: it's a wire-format change
(`SpawnSpec`, `_root_mailbox`, `_registry_addrs`) + every fixture
+ downstream config, so it wants its own migration commit landed
*before* any new backend; and it's the moment to stop handing raw
tuples to users at all — `Address` becomes the public currency
and `UnwrappedAddress` an internal serialization detail, the same
discipline `ipaddress` uses (you pass `IPv4Address`, never a
4-tuple).
Plan 01 §2.2 is rewritten to match and to explicitly **retract**
its own earlier `('tipc:<stype>:<scope>', instance)` self-tagging
prefix hack — it keeps `wrap_address()` order-sensitive and does
nothing for the iroh/UDS collision, so the doc says don't
resurrect it. Registration checklist item 4 likewise becomes "do
the migration first, then this is a one-line `_address_types`
entry".
Also seeds a `/tipc` multiaddr-spec submission as a follow-up,
mirroring the `wg` track (multiformats/py-multiaddr#107/#108 + gh
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
The prior revision (and gh #482's examples) had it as a suffix, `/ip4/10.0.11.1/tcp/1616/wg/u<key>`. Wrong: verified against `baudco/py-multiaddr@wg_support` (py-multiaddr#108) installed in a throwaway venv, the canonical form is /ip4/192.168.1.50/udp/51820/wg/u<A_pub>/ip4/10.0.11.1/tcp/1616 where segs *before* `/wg/` are the **bearer** — the underlay `(ip, udp-port)` `wg(8)` itself listens on (`ListenPort`), per the codec docstring's own example — and segs *after* are the **overlay** ep, the only part we ever bind. The suffix form does parse, which is why it slipped through, but it's semantically inverted: overlay addr where the bearer belongs, `tcp` where wg's `udp` goes, and no overlay ep declared at all. Records the observed `[p.name for p in m.protocols()]` lists so the `match` can be written against fact, and replaces the "composed vs not" framing w/ what's actually the design axis: three parts, three **owners** — bearer bound by the kernel via `wg-quick`/`pyroute2`, `/wg/u<key>` bound by nothing (it's an identity, verified out-of-band), overlay bound by our `IPCServer` as `.inner`. `_peel_tunnel_segs()` correspondingly grows a 3rd return, splitting *at* the tunnel seg so nested tunnels fall out for free. Also hoists the netns conclusion to the top of §5.3 where it can't be missed: netns is a **runtime-level config API, not an actor-app-code one**. It's a spawn/boot-time input alongside `enable_transports`/`tpt_bind_addrs`, deliberately w/ no `await actor.enter_netns(...)`, because `setns(2)` neither moves already-created sockets nor applies beyond the calling thread — so a mid-life API would silently leave the IPC server bound in the old ns. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
Re-renders gh #482's examples w/ the corrected (infix) maddr grammar, as the "layer A" slice of the wg plan: declarative maddrs only, tunnel pre-provisioned out-of-band, zero runtime changes. - `wg_maddr.py`: a `frozen=True` `msgspec.Struct` addr carrying `bearer`/`peer_pubkey`/`inner` (+ `inner_proto`), a `.maddr` property that re-renders the canonical form, and pure `mb_pubkey()`/`wg8_pubkey()`/`parse_wg_maddr()`. The parser rejects #482's inverted suffix form w/ an actionable error and stays **side-effect free** — `verify_wg_peer()` is a separate, explicitly impure step the caller composes, never something a parse path shells out to. - `host_a_srv.py`/`host_b_client.py`: the two-host runs, passing only `addr.inner` into `open_nursery()`/`open_root_actor()`, which is the whole point — the bearer + key layers are already established before any bind happens. - `README.md`: the grammar + the 3-owners table, the `#108` branch install line, tunnel setup, and a "what changed vs #482" section enumerating the corrections. Runnable-shaped but **not yet run against a live tunnel**; that's next, and the reason these sit on the planning branch rather than in `examples/` proper. `_segments()` marks its stopgap for when the `wg` codec isn't installed. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
`tests/test_docs_examples.py` walks `examples/` **recursively** and subproc-runs every collected file asserting `rc == 0`. Ran its exact filter against the tree: all 4 of our files were being collected — including `README.md`, since the filter never checks the extension, so CI would have literally tried `python README.md`. These need a real second host + a live `wg` tunnel, so they can't ever satisfy that gate. `'multihost' not in p[0]` is already in the test's exclusion list w/ no dir yet using it, so this is a pure `git mv` — zero test changes — and it's what the exclusion was plainly there for. Collection drops 24 -> 20 files, 0 of them ours. Also records *why* in the two places someone would look before adding the next one: a callout at the top of the example README and a note on plan 03's §3.4 deliverables. Anything needing a second host or live tunnel goes under `examples/multihost/`. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
One record covering all 9 commits on this branch, per the NLNet generative-AI policy and the existing `ai/prompt-io/claude/` convention. Uses diff-ref mode for both the plan docs and the example code (`git diff main..ng_tpts_planning -- <path>`) rather than duplicating content already in `git log -p`. Kept verbatim in the `.raw.md`: the four verified findings (trio's family-agnostic `SocketStream`/`SocketListener`, the round-trip table proving `/wg/` is infix, the proto-key `UnwrappedAddress` rationale, and `setns(2)`'s per-thread reality), since those are reasoning rather than diffable output. `## Human edits` records that the steering here was substantial and mid-session rather than post-hoc: two model claims about wg maddr semantics were challenged and retracted (incl. in an already-posted issue comment), and the proto-key + netns-as-runtime-config framings were human-directed. Also notes the one model-initiated correction — a pre-publication self-review that downgraded the `uniffi`/asyncio thesis and the TIPC duplicate-binder claim to explicitly-flagged assumptions. Prompt-IO: ai/prompt-io/claude/20260813T001102Z_27c34aeb_prompt_io.md (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
There was a problem hiding this comment.
Pull request overview
Adds planning documentation for “next-gen” tractor.ipc transport backends (TIPC, QUIC/iroh, WireGuard-as-bindspace), plus a two-host WireGuard multiaddr example under examples/multihost/.
Changes:
- Add a set of detailed backend plan/spec markdown docs under
ai/tpt-backends/. - Add WireGuard tunnelled-multiaddr parsing + verification helper and two multihost example scripts.
- Add prompt I/O artifacts capturing the doc-generation session output.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| examples/multihost/wg_lan/wg_maddr.py | Example-local WG tunnelled-maddr struct + parse/render helpers + optional peer verification |
| examples/multihost/wg_lan/README.md | Setup and usage guide for the multihost WireGuard example and the maddr grammar |
| examples/multihost/wg_lan/host_a_srv.py | Host A server-side example binding tractor on the WG overlay endpoint |
| examples/multihost/wg_lan/host_b_client.py | Host B client-side example dialing Host A via the WG overlay endpoint |
| ai/tpt-backends/README.md | Index/overview of the backend plan docs and their sequencing |
| ai/tpt-backends/00_shared_backend_contract.md | Shared “backend contract” doc: conventions, registration checklist, dependency policy, test harness notes |
| ai/tpt-backends/01_tipc_backend.md | Plan/spec for a TIPC backend using kernel discovery semantics |
| ai/tpt-backends/02_quic_iroh_backend.md | Plan/spec for QUIC via iroh, including an approach to a trio-native uniffi bridge |
| ai/tpt-backends/03_wg_tunnel_bindspace.md | Plan/spec for WG as a nested bindspace and tunnelled multiaddr grammar |
| ai/prompt-io/claude/20260813T001102Z_27c34aeb_prompt_io.raw.md | Raw captured output for the doc-generation session |
| ai/prompt-io/claude/20260813T001102Z_27c34aeb_prompt_io.md | Curated summary of the doc-generation session output |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+92
to
+94
| import multibase | ||
| raw: bytes = base64.b64decode(wg8_key) | ||
| return multibase.encode('base64url', raw).decode('ascii') |
Comment on lines
+127
to
+130
| bearer_segs: list[str] = segs[:wg_at] | ||
| mb_key: str = segs[wg_at + 1] | ||
| inner_segs: list[str] = segs[wg_at + 2:] | ||
|
|
Comment on lines
+78
to
+84
| b_host, b_port = self.bearer | ||
| i_host, i_port = self.inner | ||
| return ( | ||
| f'/ip4/{b_host}/udp/{b_port}' | ||
| f'/wg/{mb_pubkey(self.peer_pubkey)}' | ||
| f'/ip4/{i_host}/{self.inner_proto}/{i_port}' | ||
| ) |
TIPC/QUIC/wg tpt backends
py-multiaddr#108 (the `/wg/u<key>` maddr proto) merged upstream on 2026-07-28 as `f86519da`, but ships in no release yet — the latest `0.2.0` predates it by ~4 months and carries no `wg` codec at all. So `examples/multihost/wg_lan/` can't parse its own maddrs off PyPI. Pinned by `rev` and not `branch` so CI stays reproducible. Note the lock now records the git source *instead of* the `>=0.2.0` specifier, i.e. the dep floor above is fully overridden for as long as this pin lives. TODO, drop the pin (and bump that floor) the moment a release carries the codec; the only consumer is the `wg_lan` example set. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
`_segments()` called `Multiaddr(maddr)` purely to validate, then swallowed every failure under `except Exception: pass`. That was harmless pre-#108 — w/o a `wg` codec there was nothing to validate — but now that the codec is pinned in, the swallow is load-bearing and disabled: a malformed key sails past validation into `wg8_pubkey()`, which happily emits a corrupt b64 str, and the returned struct then fails its own `.maddr` round-trip. No raise, just quietly wrong output. Deats, - add `_have_wg_maddr_proto()`, the gate plan-03 already referenced but which never actually existed. Impl'd as `protocols.protocol_with_name('wg')` under `except ProtocolNotFoundError` and cached in a mod global, same shape as the TIPC plan's `is_tipc_available()`. - only validate when that gate is `True`, and let `StringParseError` propagate — a maddr which doesn't parse must NOT reach `wg8_pubkey()`. - keep the degraded split for a pre-#108 install, now w/ an explicit `XXX` naming the validation you give up. So parsing stays pure but becomes total-or-raises. Our own `ValueError`s (missing `/wg/` seg, bare tunnel w/o an overlay ep) are unaffected, as is the `wg(8)` b64 round-trip. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
it lands" framing in plan-03 and the example README was stale in both directions: the branch pin is obsolete, yet you still can't just `pip install multiaddr`. Deats, - §3.2's grammar table is now re-verified against the upstream merge (`f86519da`) rather than only `baudco@wg_support` in a throwaway venv. Also notes the codec enforces a 32-byte key, so a truncated one is a `StringParseError` and not a silently mangled parse. - §1 says merged-but-unreleased; the still-open work is spec registration (py-multiaddr#107 + gh #483). - §3.4 swaps "pin the branch" for the `[tool.uv.sources]` `rev` pin, and fixes the `_have_wg_maddr_proto()` recipe it suggested — probing w/ `Multiaddr('/wg/uAAAA')` now ALWAYS raises bc the codec wants 32B, i.e. that feature-detect would report `False` even w/ the proto perfectly well known. - risk table row goes "#108 not merged" -> "merged but unreleased". - example README: `uv sync` alone now suffices bc of the pin; documents the 32B check and points at `_have_wg_maddr_proto()` as the gate. The one surviving `baudco` mention is deliberate, it records where the grammar was *first* verified. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add impl plans for
TIPC/QUIC/wgtpt backendsMotivation
Three prospective tpt backends have been sitting in issues as
sketches — TIPC (#378), QUIC-via-
iroh(#353), andwg(#482,#443) — each with enough design surface that starting to code any
one of them means re-deriving the same
.ipccontract from scratch,and probably deriving it differently each time. Worse, two of the
three quietly break assumptions the current 2-backend
_addr.pygetsaway with, and you don't find that out until you're deep in an impl.
So this is planning work landed as docs: no runtime code is
touched. The point is to get the shared contract and the three
designs reviewed before anyone writes a backend against them,
because two conclusions below are wire-format-affecting and much
cheaper to argue about now than after a backend ships.
Everything here is written so a different contributor — or a
different model — can pick up any one plan and work it independently,
w/o the other two landing first.
Src of research
Verified against sources rather than recalled; the plans treat kernel
headers + the upstream PRs as normative over docs sites,
wg(WireGuard) protocol support multiformats/py-multiaddr#108wgcodec docstring, which settles the maddr grammar'wg'?) multiformats/py-multiaddr#107include/uapi/linux/tipc.h+net/tipc/in-treeuniffiglue for the async storytrio's_highlevel_socket.pySummary of changes
ai/tpt-backends/— a normative shared backend contract specplus one plan doc per prospective backend, indexed by a
README.The contract pins the duck-type every backend must satisfy (a
frozen
msgspec.Struct<Proto>Address, module-levelstart_listener()/close_listener(), aMsgpack<Proto>Stream(MsgpackTransport)), documents theinspect.getmodule(self.addr)reflection inEndpoint.start_listener()that forces anAddressclass and itslistener fns to live in the same mod, and carries a 10-item
registration checklist so adding a backend is mechanical.
TIPCas the cheapest of the three: verified thattrio'sSocketStream/SocketListenerare address-family agnostic —the only ctor checks are "is a trio sock" +
type == SOCK_STREAMplus an
OSError-suppressedSO_ACCEPTCONNprobe, w/ noAF_*check anywhere — so a TIPC
SOCK_STREAMsock drops straight intotrio.serve_listeners()on our existing framing, zero new deps.The interesting part is discovery:
bind()is registration andconnect()is lookup, w/TIPC_TOP_SRVsubscriptions as apush-based registry.
QUIConiroh(overaioquic/quiche) for node-idaddressing + hole punching + relay fallback, w/ the
asyncioquestion answered by a ~40-line
TrioToken.run_sync_soon()bridgerather than
trio-asyncio. Since an iroh listener isn't a sock, italso needs
trio.abc.Listener/HalfCloseableStreamadapters + asmall prep patch to
_server.py/_types.py— landable w/ tcp+udsas the only backends.
wgas a bindspace, not a tpt. It's transparent tosocket(2), so it belongs as a scoped@acm-managed net ctx thatan existing L4 tpt binds inside, via a
TunnelledAddresswrapperwhose
.proto_key/.unwrap()delegate to.inner— nothing newcrosses the wire and every existing table lookup keeps working.
Three independently shippable layers: declarative maddrs,
pyroute2replacing thesudo wg showshelling, then thenetns/iface
@acms.wgmaddr grammar thewgmultiaddr: LAN deployment examples #482 examples used:/wg/isinfix, not suffix. Installed
baudco/py-multiaddr@wg_supportin a throwaway venv and round-tripped each candidate form — segs
before
/wg/are the bearer (the underlay(ip, udp-port)wg(8)itself listens on), segs after are the overlay ep, andonly that last part is ours to bind. The suffix form parses, which
is why it slipped through, but it's semantically inverted.
wrap_address()does not survive4 backends and propose proto-key-tagging the unwrapped form.
TIPC's natural unwrapped shape is a
(str, int), indistinguishablefrom
TCPAddress; iroh's is a(str, str), already swallowed bythe existing UDS case. The fix is an explicit proto-key spelled w/
the
multiaddrproto name —('tcp', host, port),('unix', path),('tipc', stype, inst, scope)— collapsingwrap_address()to_address_types[addr[0]].examples/multihost/wg_lan/— thewgmultiaddr: LAN deployment examples #482 example set re-renderedagainst the corrected grammar, as the plan's layer A. A frozen
msgspectunnelled addr w/ pure parse/render helpers, anexplicitly-impure
verify_wg_peer()kept out of the parse path,and the two host scripts passing only
addr.innerto the runtime.They live under
multihost/bctest_docs_examples.pywalksexamples/recursively and subproc-runs everything it collects(w/o even filtering by extension) —
'multihost'is already in itsexclusion list.
ai/prompt-io/claude/per the NLNetpolicy, in diff-ref mode so the code isn't duplicated.
Two claims in here are deliberately marked as unverified
assumptions rather than findings, and are the first things worth
attacking: that TIPC accepts duplicate binders on a name and
round-robins (making an instance collision silent cross-talk rather
than
EADDRINUSE), and thatuniffiusesasyncioonly as theexecutor for a rust-future poll loop. Both are gated behind a step-0
probe in their respective plans.
TODOs before landing
Docs-only, so nothing is mechanically blocking — but these are design
calls that get much more expensive after a backend ships, and are the
reason this is up for review rather than merged:
UnwrappedAddress. It's awire-format change (
SpawnSpec,_root_mailbox,_registry_addrs) plus every fixture and downstream config(
piker's[network]table). Land it before the first newbackend, or after?
wgis a bindspace rather than a tpt — thewgmultiaddr: LAN deployment examples #482 sketch assumed aWGAddressin_address_types, and the planargues that breaks the table's 1:1 intent.
irohselection, given it drags in an FFIbridge.
aioquicstays documented as the fallback and the adaptersare shaped to be ~90% reusable against a sans-io core.
Channel(as planned) vs one per
tractor.Contextfor independentflow-control + cancellation.
Future follow up
land the proto-key
UnwrappedAddressmigration — its ownbranch off
main, and the natural base for the three backend PRssince they all depend on it. See contract §1.1 for the blast
radius.
stop handing raw unwrapped tuples to users — make
Addressthe public currency and
UnwrappedAddressan internalserialization detail, the discipline
ipaddressuses. Public APIaccepts
Address|maddr-str; bare tuples become legacy-tolerated,ideally deprecated.
run
examples/multihost/wg_lan/against a live tunnel —they're runnable-shaped but have never touched real
wgifaces.Until they do, treat them as executable pseudo-code.
register
/tipcin the multiaddr spec — mirrors thewgtrack (py-multiaddr#107/Multiple issues on the code examples present in the README file. #108, tractor
wgmultiaddr protocol: upstream spec submission plan #483). Worth proposingalongside the
wgsubmission rather than as a one-off.settle how an iroh node-id is spelled as a maddr —
/p2p/…? It's the third unregistered proto blocking theMsgTransport.maddrstandardization in Follow-up: multiaddr_support (PR #429) #443.implement
Address.namespace— spec'd in theAddressprotocol since day one and implemented by no backend; there's
already a
!TODO, always be ns aware!+|_netns:placeholdersitting in
Endpoint.pformat(). Thewg/netns work is its firstconsumer.
veth-in-netns as the first bindspace, beforewg—plan 03 argues it makes a fully self-contained two-"host"
integration test possible w/o
wgat all.Links
TIPC, the "end game" cluster proto maybe? #378 — TIPC backendQUICtransport research #353 — QUIC backendwgmultiaddr: LAN deployment examples #482 —wgmaddr examples (comment on this branch'scorrections)
multiaddr_supportfollow-ups; body updated w/ thecorrected tunnelled-maddr grammar
wgmultiaddr protocol: upstream spec submission plan #483 —wgmultiaddr spec submission planwg(WireGuard) protocol support multiformats/py-multiaddr#108(this pr content was generated in some part by
claude-codeusingclaude-opus-5(anthropic))