Widen `UnwrappedAddress` to admit proto-keyed addrs

`SpawnSpec.reg_addrs`/`.bind_addrs` pinned the wire shape to
a 2-tuple, so a `tipc` addr (`('tipc', stype, inst, scope)`)
died at the child w/ `msgspec.ValidationError: Expected array
of length 2, got 4` -> `invalid SpawnSpec IPC msg`.

Point those fields at `UnwrappedAddress` (which `SpawnSpec`s
own TODO already asked for) and widen the alias.

XXX VARIADIC (`tuple[str|int, ...]`) rather than a union of
the two concrete shapes, bc `msgspec` refuses a union holding
more than one array-like type.

?TODO, the real fix is the full proto-key migration (contract
§1.1) after which this becomes a tagged union keyed off elem
0 and per-proto validation comes back.

Note the alias is declared TWICE — `.msg.types` re-declares it
to dodge a circular import (`._addr` -> `.ipc._tcp` -> `.msg`)
and *that* copy is what actually validates the wire msg.

(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
wkt/pr493_review
Gud Boi 2026-08-14 09:52:33 -04:00
parent 8c0ae140cd
commit 2204979492
2 changed files with 44 additions and 5 deletions

View File

@ -65,9 +65,22 @@ log = get_logger()
#
UnwrappedAddress = (
# tcp/udp/uds
# ('127.0.0.1', 1616)
# ('/run/user/1000/tractor', 'registry@1616.sock')
#
# ..and the explicitly proto-keyed (`multiaddr`-spelled)
# form, which is where ALL backends should eventually land
# per the note below,
# ('tipc', 1953628160, 1616, 2)
#
# XXX VARIADIC bc `msgspec` refuses a union of >1 array-like
# type, so the two shapes can't be spelled as a union. Keep
# in sync with `.msg.types.UnwrappedAddress` which
# re-declares this to dodge a circular import AND is what
# actually validates the `SpawnSpec` wire msg!
tuple[
str, # host/domain(tcp), filesys-dir(uds)
int|str, # port/path(uds)
str|int,
...,
]
# ?TODO? should we also include another 2 fields from
# our `Aid` msg such that we include the runtime `Actor.uid`
@ -82,6 +95,7 @@ UnwrappedAddress = (
class Address(Protocol):
proto_key: ClassVar[str]
unwrapped_type: ClassVar[UnwrappedAddress]
# whether `.ipc._server.Endpoint.start_listener()` should
# reconcile a bound `.addr` against its listener's
# `socket.getsockname()`.
@ -182,7 +196,7 @@ class Address(Protocol):
_address_types: bidict[str, Type[Address]] = {
'tcp': TCPAddress,
'uds': UDSAddress
'uds': UDSAddress,
}

View File

@ -188,6 +188,31 @@ class Aid(
__repr__ = pretty_struct.Struct.__repr__
# NOTE, mirrors `.discovery._addr.UnwrappedAddress` but is
# re-declared here to dodge the circular import
# (`._addr` -> `.ipc._tcp` -> `.msg`).
#
# XXX this is the **wire** shape, so widening it is a wire-format
# change; keep the two decls in sync.
# XXX VARIADIC on purpose! `msgspec` rejects a union holding
# more than one array-like type ("Type unions may not contain
# more than one array-like (list, set, tuple) type"), so the two
# concrete shapes,
#
# ('127.0.0.1', 1616) # tcp
# ('/run/user/1000/tractor', 'x.sock') # uds
# ('tipc', 1953628160, 1616, 2) # proto-keyed (tipc)
#
# can't be spelled as `tuple[str, str|int]|tuple[str, int, int,
# int]`. Widen to one homogeneous variadic tuple instead.
#
# ?TODO, the real fix is the `UnwrappedAddress` proto-key
# migration (see `.discovery._addr`) after which this becomes a
# tagged union keyed off elem 0 and full per-proto validation
# comes back.
UnwrappedAddress = tuple[str|int, ...]
class SpawnSpec(
pretty_struct.Struct,
tag=True,
@ -213,8 +238,8 @@ class SpawnSpec(
# TODO: not just sockaddr pairs?
# -[ ] abstract into a `TransportAddr` type?
reg_addrs: list[tuple[str, str|int]]
bind_addrs: list[tuple[str, str|int]]|None
reg_addrs: list[UnwrappedAddress]
bind_addrs: list[UnwrappedAddress]|None
# TODO: caps based RPC support in the payload?