Proto-key the unwrapped-addr form in the plans
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`))
ng_tpts_planning
parent
f60bdc44c7
commit
208876cc3b
|
|
@ -70,6 +70,40 @@ Hard constraints learned from the existing two:
|
||||||
`wrap_address()` (`_addr.py:230`), you have a bug that
|
`wrap_address()` (`_addr.py:230`), you have a bug that
|
||||||
manifests as the wrong transport being loaded — the file's own
|
manifests as the wrong transport being loaded — the file's own
|
||||||
`XXX NOTE` warns about precisely this.
|
`XXX NOTE` warns about precisely this.
|
||||||
|
|
||||||
|
⚠️ **and shape-matching does not survive 4 backends.** Adding
|
||||||
|
TIPC and iroh breaks it outright: TIPC's natural 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. Ordering hacks and prefix-tagging (an earlier
|
||||||
|
revision of plan 01 proposed `('tipc:<stype>:<scope>', inst)`)
|
||||||
|
paper over it at best.
|
||||||
|
|
||||||
|
**The fix, and the recommended prerequisite for all three
|
||||||
|
backends: make the unwrapped form carry an explicit
|
||||||
|
proto-key, using the `multiaddr` protocol name as the
|
||||||
|
canonical spelling** — `('tcp', host, port)`,
|
||||||
|
`('unix', path)`, `('udp', ...)`, `('tipc', stype, inst,
|
||||||
|
scope)`. Then `wrap_address()` collapses from an
|
||||||
|
order-sensitive `match` to `_address_types[addr[0]]`, and the
|
||||||
|
whole collision class stops existing. Note this *also* aligns
|
||||||
|
the on-wire form with `mk_maddr()`/`parse_maddr()`, so the two
|
||||||
|
representations stop being independent inventions.
|
||||||
|
|
||||||
|
Two consequences to plan for:
|
||||||
|
- it's a **wire-format change** (`SpawnSpec`,
|
||||||
|
`_root_mailbox`, `_registry_addrs`) plus every test fixture
|
||||||
|
and downstream config (`piker`'s `[network]` table). It
|
||||||
|
wants its **own migration commit, landed before any new
|
||||||
|
backend**, not smuggled into one.
|
||||||
|
- it's the moment to **stop handing raw unwrapped tuples to
|
||||||
|
users at all.** The long-term shape is: `Address` subtypes
|
||||||
|
are the public currency and `UnwrappedAddress` becomes an
|
||||||
|
internal serialization detail — the same discipline
|
||||||
|
`ipaddress` uses (you pass `IPv4Address`, not a 4-tuple).
|
||||||
|
Public API should accept `Address|maddr-str` and treat bare
|
||||||
|
tuples as legacy-tolerated input, ideally deprecated.
|
||||||
- **`.get_random()` must be collision-free without a live
|
- **`.get_random()` must be collision-free without a live
|
||||||
runtime.** See the `UDSAddress.get_random()` uuid-token
|
runtime.** See the `UDSAddress.get_random()` uuid-token
|
||||||
comment (`_uds.py:207-220`): with no `current_actor()` the
|
comment (`_uds.py:207-220`): with no `current_actor()` the
|
||||||
|
|
@ -214,8 +248,9 @@ Adding a backend touches these and only these:
|
||||||
entry lazy — propose that refactor explicitly.
|
entry lazy — propose that refactor explicitly.
|
||||||
4. `tractor/discovery/_addr.py:230` `wrap_address()` `match` —
|
4. `tractor/discovery/_addr.py:230` `wrap_address()` `match` —
|
||||||
add a case iff your `unwrapped_type` isn't already uniquely
|
add a case iff your `unwrapped_type` isn't already uniquely
|
||||||
matched. Prefer unwrapped forms that are *self-tagging*
|
matched. **Preferably do the proto-key migration in §1.1
|
||||||
(see plan 01 §2.2 and plan 02 §2.2) so this stays cheap.
|
first**, after which this step becomes a one-line
|
||||||
|
`_address_types` entry instead of an order-sensitive `case`.
|
||||||
5. `tractor/ipc/_types.py` — `Address` union alias,
|
5. `tractor/ipc/_types.py` — `Address` union alias,
|
||||||
`_msg_transports` list, `_key_to_transport[('msgpack', key)]`,
|
`_msg_transports` list, `_key_to_transport[('msgpack', key)]`,
|
||||||
`_addr_to_transport[<Proto>Address]`.
|
`_addr_to_transport[<Proto>Address]`.
|
||||||
|
|
|
||||||
|
|
@ -98,34 +98,34 @@ class TIPCAddress(
|
||||||
def_bindspace: ClassVar[int] = TIPC_CLUSTER_SCOPE
|
def_bindspace: ClassVar[int] = TIPC_CLUSTER_SCOPE
|
||||||
```
|
```
|
||||||
|
|
||||||
**Unwrapped form** (the wire/`SpawnSpec` shape) — must be
|
**Unwrapped form** (the wire/`SpawnSpec` shape).
|
||||||
uniquely matchable in `wrap_address()`:
|
|
||||||
|
TIPC's natural form is `(stype, instance, scope)` — but a
|
||||||
|
2-tuple squeeze of it is a `(str, int)`, i.e. *the same coarse
|
||||||
|
shape as `TCPAddress`*, so `wrap_address()`'s
|
||||||
|
`case (str(), int())` steals it. This backend is therefore the
|
||||||
|
forcing function for the contract-doc's conclusion (§1.1):
|
||||||
|
|
||||||
|
> **make the unwrapped form carry an explicit proto-key, spelled
|
||||||
|
> with the `multiaddr` protocol name.**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def unwrap(self) -> tuple[str, int]:
|
def unwrap(self) -> tuple[str, int, int, int]:
|
||||||
# ('tipc:<stype>:<scope>', instance)
|
return ('tipc', self._stype, self._instance, self._scope)
|
||||||
return (f'tipc:{self._stype}:{self._scope}', self._instance)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
i.e. `(str, int)` — *the same coarse shape as `TCPAddress`*, so
|
`wrap_address()` then dispatches `_address_types[addr[0]]` and
|
||||||
`wrap_address()`'s `case (str(), int())` would steal it. Two
|
the collision class disappears. **This is a prerequisite
|
||||||
options; **pick (a)**:
|
migration commit, not part of this backend** — see contract §1.1
|
||||||
|
for its blast radius (wire format + every fixture + `piker`
|
||||||
|
config) and for the follow-on "stop handing raw tuples to users
|
||||||
|
at all, à la `ipaddress`" direction.
|
||||||
|
|
||||||
- **(a) self-tagging prefix + a `case` ordered before the TCP
|
⚠️ an earlier revision of this plan proposed a self-tagging
|
||||||
one**, guarded on `addr[0].startswith('tipc:')`:
|
`('tipc:<stype>:<scope>', instance)` string-prefix hack with an
|
||||||
```python
|
ordered `case` guard. **Dropped** — it papers over the problem,
|
||||||
case (str() as h, int()) if h.startswith('tipc:'):
|
keeps `wrap_address()` order-sensitive, and doesn't help iroh's
|
||||||
cls = TIPCAddress
|
`(str, str)`-vs-UDS collision at all. Do not resurrect it.
|
||||||
```
|
|
||||||
Cheap, string-y, but honest about the fact that
|
|
||||||
`UnwrappedAddress` is a deliberately-degenerate
|
|
||||||
`tuple[str, int|str]` (see the `_addr.py:43-65` TODO block).
|
|
||||||
It also keeps `TCPAddress.__post_init__`'s
|
|
||||||
`ipaddress.ip_address()` validation from being reached with a
|
|
||||||
non-IP host.
|
|
||||||
- (b) widen `UnwrappedAddress` to a 3-tuple for tipc. Rejected:
|
|
||||||
ripples into every `reg_addr` fixture, `_root_mailbox`, and
|
|
||||||
downstream (`piker`) config.
|
|
||||||
|
|
||||||
Note `TIPCAddress` is the first backend where `.unwrap()` is
|
Note `TIPCAddress` is the first backend where `.unwrap()` is
|
||||||
**not** a lossless view of the live socket — `maybe_node`/
|
**not** a lossless view of the live socket — `maybe_node`/
|
||||||
|
|
@ -689,6 +689,12 @@ single best demo this backend has; lead with it.
|
||||||
|
|
||||||
## 10. Follow-up issue seeds
|
## 10. Follow-up issue seeds
|
||||||
|
|
||||||
|
- **register `/tipc` in the multiaddr spec**, mirroring the `wg`
|
||||||
|
track (multiformats/py-multiaddr#107/#108 + gh #483). Same
|
||||||
|
shape of work: propose the proto + code, land a codec in
|
||||||
|
`py-multiaddr`, then drop our `str`-maddr fallback (§4). Worth
|
||||||
|
filing *alongside* the `wg` spec-submission issue so both
|
||||||
|
proposals go up together rather than as one-offs.
|
||||||
- registrar-less discovery fast path via name derivation (§5.1)
|
- registrar-less discovery fast path via name derivation (§5.1)
|
||||||
- `TIPC_TOP_SRV`-driven push registry in
|
- `TIPC_TOP_SRV`-driven push registry in
|
||||||
`discovery/_registry.py` (§5.2)
|
`discovery/_registry.py` (§5.2)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue