166 lines
7.1 KiB
Markdown
166 lines
7.1 KiB
Markdown
|
|
---
|
||
|
|
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
|
||
|
|
(`<Proto>Address(msgspec.Struct, frozen=True)` + module-level
|
||
|
|
`start_listener()`/`close_listener()` + a
|
||
|
|
`Msgpack<Proto>Stream(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<k>` | `['ip4','udp','wg']` |
|
||
|
|
| `/ip4/../udp/../wg/u<k>/ip4/../tcp/..` | `['ip4','udp','wg','ip4','tcp']` |
|
||
|
|
| `/ip4/10.0.11.1/tcp/1616/wg/u<k>` | `['ip4','tcp','wg']` |
|
||
|
|
|
||
|
|
```
|
||
|
|
/ip4/192.168.1.50/udp/51820/wg/u<A_pub>/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<key>` | 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).
|