Normalize registrar addrs with `wrap_address()`
Canonicalize legacy socket pairs and tagged tuples before storing or comparing `Registry.addrs`, keeping existing config readable when tractor emits protocol-tagged forms. Also, - broaden registry/runtime addr annotations for both wire shapes - compare service fixture addrs through tractor's address codec - use tractor's shared `--ll` pytest option instead of registering it twice - cover mixed legacy/tagged registry state and `pikerd` startup Prompt-IO: ai/prompt-io/opencode/20260820T163519Z_5f631e19_prompt_io.md (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))wkt/tractor_tagged_addrs_compat
parent
5f631e1996
commit
b7aca4c0dc
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
model: gpt-5.6-sol
|
||||
service: opencode
|
||||
session: tractor-addr-unpacking
|
||||
timestamp: 2026-08-20T16:35:19Z
|
||||
git_ref: 5f631e19
|
||||
scope: code
|
||||
substantive: true
|
||||
raw_file: 20260820T163519Z_5f631e19_prompt_io.raw.md
|
||||
---
|
||||
|
||||
## Prompt
|
||||
|
||||
The human asked to implement the Piker compatibility work caused by
|
||||
tractor's canonical tagged-address migration, run the previously
|
||||
deferred tractor verification, and keep runtime-managed WireGuard
|
||||
bindspaces in a later separate PR.
|
||||
|
||||
## Response summary
|
||||
|
||||
Made Piker normalize legacy and tagged registry inputs before semantic
|
||||
comparison and process-global storage. Updated runtime address typing and
|
||||
test assertions for canonical output, retained legacy caller input, and
|
||||
removed a duplicate pytest log-level option that blocked downstream test
|
||||
collection with current tractor.
|
||||
|
||||
## Files changed
|
||||
|
||||
- `piker/service/_registry.py` - canonical registry normalization and
|
||||
shared address typing.
|
||||
- `piker/service/_actor_runtime.py` - tagged-compatible runtime API
|
||||
annotations.
|
||||
- `tests/conftest.py` - canonical fixture comparison and shared pytest
|
||||
option ownership.
|
||||
- `tests/test_services.py` - registry regression and canonical runtime
|
||||
assertions.
|
||||
|
||||
## Human edits
|
||||
|
||||
The human explicitly requested the downstream implementation and chose
|
||||
to keep WireGuard bindspace lifecycle work in a separate future tractor
|
||||
PR. The agent implemented and tested those directions; no direct manual
|
||||
source edits were observed.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
model: gpt-5.6-sol
|
||||
service: opencode
|
||||
timestamp: 2026-08-20T16:35:19Z
|
||||
git_ref: 5f631e19
|
||||
diff_cmd: git diff HEAD~1..HEAD
|
||||
---
|
||||
|
||||
# Raw output - tractor tagged-address compatibility
|
||||
|
||||
The human requested downstream Piker qualification after tractor PR
|
||||
#505 switched transport address emission to canonical tagged tuples.
|
||||
|
||||
> `git diff HEAD~1..HEAD -- piker/service/_registry.py piker/service/_actor_runtime.py tests/conftest.py tests/test_services.py`
|
||||
|
||||
Normalized legacy and tagged registry addresses through tractor's
|
||||
address codec before Piker stores or compares them. Extended service
|
||||
runtime annotations to cover both tuple shapes, updated runtime tests to
|
||||
compare canonical serialized values, and removed Piker's duplicate
|
||||
registration of tractor's shared `--ll` pytest option.
|
||||
|
||||
Ruff passed. The focused normalization regression and Piker runtime boot
|
||||
passed against the local tractor checkout at `5d92595f`. The wider
|
||||
service module reached an unrelated pre-existing logger-name assertion
|
||||
in `maybe_spawn_daemon()` after both address-focused tests passed.
|
||||
|
|
@ -44,6 +44,7 @@ from ._registry import ( # noqa
|
|||
_tractor_kwargs,
|
||||
_default_reg_addr,
|
||||
open_registry,
|
||||
RegistryAddress,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -58,7 +59,7 @@ def get_runtime_vars() -> dict[str, Any]:
|
|||
@acm
|
||||
async def open_piker_runtime(
|
||||
name: str,
|
||||
registry_addrs: list[tuple[str, int]] = [],
|
||||
registry_addrs: list[RegistryAddress] = [],
|
||||
tpt_bind_addrs: list|None = None,
|
||||
|
||||
enable_modules: list[str] = [],
|
||||
|
|
@ -77,7 +78,7 @@ async def open_piker_runtime(
|
|||
|
||||
) -> tuple[
|
||||
tractor.Actor,
|
||||
list[tuple[str, int]],
|
||||
list[RegistryAddress],
|
||||
]:
|
||||
'''
|
||||
Start a piker actor who's runtime will automatically sync with
|
||||
|
|
@ -168,7 +169,7 @@ _root_modules: list[str] = [
|
|||
|
||||
@acm
|
||||
async def open_pikerd(
|
||||
registry_addrs: list[tuple[str, int]],
|
||||
registry_addrs: list[RegistryAddress],
|
||||
tpt_bind_addrs: list|None = None,
|
||||
loglevel: str|None = None,
|
||||
|
||||
|
|
@ -272,7 +273,7 @@ async def open_pikerd(
|
|||
|
||||
@acm
|
||||
async def maybe_open_pikerd(
|
||||
registry_addrs: list[tuple[str, int]] | None = None,
|
||||
registry_addrs: list[RegistryAddress] | None = None,
|
||||
|
||||
loglevel: str | None = None,
|
||||
**kwargs,
|
||||
|
|
@ -306,7 +307,7 @@ async def maybe_open_pikerd(
|
|||
# async with open_portal(chan) as arb_portal:
|
||||
# yield arb_portal
|
||||
|
||||
registry_addrs: list[tuple[str, int]] = (
|
||||
registry_addrs: list[RegistryAddress] = (
|
||||
registry_addrs
|
||||
or
|
||||
[_default_reg_addr]
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from contextlib import (
|
|||
)
|
||||
from typing import (
|
||||
Any,
|
||||
TypeAlias,
|
||||
)
|
||||
|
||||
import tractor
|
||||
|
|
@ -32,11 +33,23 @@ from tractor import (
|
|||
Actor,
|
||||
Portal,
|
||||
)
|
||||
from tractor.discovery._addr import (
|
||||
LegacyUnwrappedAddress,
|
||||
UnwrappedAddress,
|
||||
wrap_address,
|
||||
)
|
||||
|
||||
from piker.log import get_logger
|
||||
|
||||
log = get_logger(name=__name__)
|
||||
|
||||
# Piker config still accepts legacy socket pairs while tractor now
|
||||
# emits tagged `UnwrappedAddress` values.
|
||||
RegistryAddress: TypeAlias = (
|
||||
LegacyUnwrappedAddress
|
||||
|UnwrappedAddress
|
||||
)
|
||||
|
||||
# TODO? default path-space for UDS registry?
|
||||
# [ ] needs to be Xplatform tho!
|
||||
# _default_registry_path: Path = (
|
||||
|
|
@ -46,10 +59,7 @@ log = get_logger(name=__name__)
|
|||
|
||||
_default_registry_host: str = '127.0.0.1'
|
||||
_default_registry_port: int = 6116
|
||||
_default_reg_addr: tuple[
|
||||
str,
|
||||
int, # |str TODO, once we support UDS, see above.
|
||||
] = (
|
||||
_default_reg_addr: RegistryAddress = (
|
||||
_default_registry_host,
|
||||
_default_registry_port,
|
||||
)
|
||||
|
|
@ -63,24 +73,37 @@ _registry: Registry | None = None
|
|||
class Registry:
|
||||
# TODO: should this be a set or should we complain
|
||||
# on duplicates?
|
||||
addrs: list[tuple[str, int]] = []
|
||||
addrs: list[RegistryAddress] = []
|
||||
|
||||
# TODO: table of uids to sockaddrs
|
||||
peers: dict[
|
||||
tuple[str, str],
|
||||
tuple[str, int],
|
||||
RegistryAddress,
|
||||
] = {}
|
||||
|
||||
|
||||
_tractor_kwargs: dict[str, Any] = {}
|
||||
|
||||
|
||||
def _normalize_addresses(
|
||||
addrs: list[RegistryAddress],
|
||||
) -> list[RegistryAddress]:
|
||||
'''
|
||||
Normalize legacy and tagged addresses through tractor's codec.
|
||||
|
||||
'''
|
||||
return [
|
||||
wrap_address(addr).unwrap()
|
||||
for addr in addrs
|
||||
]
|
||||
|
||||
|
||||
@acm
|
||||
async def open_registry(
|
||||
addrs: list[tuple[str, int]],
|
||||
addrs: list[RegistryAddress],
|
||||
ensure_exists: bool = True,
|
||||
|
||||
) -> list[tuple[str, int]]:
|
||||
) -> list[RegistryAddress]:
|
||||
'''
|
||||
Open the service-actor-discovery registry by returning a set of
|
||||
tranport socket-addrs to registrar actors which may be
|
||||
|
|
@ -92,9 +115,12 @@ async def open_registry(
|
|||
actor: Actor = tractor.current_actor()
|
||||
aid: msg.Aid = actor.aid
|
||||
uid: tuple[str, str] = aid.uid
|
||||
preset_reg_addrs: list[
|
||||
tuple[str, int]
|
||||
] = Registry.addrs
|
||||
addrs = _normalize_addresses(addrs)
|
||||
preset_reg_addrs: list[RegistryAddress] = (
|
||||
_normalize_addresses(Registry.addrs)
|
||||
)
|
||||
if preset_reg_addrs:
|
||||
Registry.addrs = preset_reg_addrs
|
||||
if (
|
||||
preset_reg_addrs
|
||||
and
|
||||
|
|
@ -102,9 +128,10 @@ async def open_registry(
|
|||
):
|
||||
if preset_reg_addrs != addrs:
|
||||
# if any(addr in preset_reg_addrs for addr in addrs):
|
||||
diff: set[
|
||||
tuple[str, int]
|
||||
] = set(preset_reg_addrs) - set(addrs)
|
||||
diff: set[RegistryAddress] = (
|
||||
set(preset_reg_addrs)
|
||||
-set(addrs)
|
||||
)
|
||||
if diff:
|
||||
log.warning(
|
||||
f'`{uid}` requested only subset of registrars: {addrs}\n'
|
||||
|
|
@ -123,7 +150,9 @@ async def open_registry(
|
|||
and
|
||||
not Registry.addrs
|
||||
):
|
||||
Registry.addrs.extend(actor.reg_addrs)
|
||||
Registry.addrs.extend(
|
||||
_normalize_addresses(actor.reg_addrs)
|
||||
)
|
||||
|
||||
if (
|
||||
ensure_exists
|
||||
|
|
@ -139,13 +168,13 @@ async def open_registry(
|
|||
Registry.addrs = (
|
||||
addrs
|
||||
or
|
||||
[_default_reg_addr]
|
||||
_normalize_addresses([_default_reg_addr])
|
||||
)
|
||||
|
||||
# NOTE: only spot this seems currently used is inside
|
||||
# `.ui._exec` which is the (eventual qtloops) bootstrapping
|
||||
# with guest mode.
|
||||
reg_addrs: list[tuple[str, str|int]] = Registry.addrs
|
||||
reg_addrs: list[RegistryAddress] = Registry.addrs
|
||||
# !TODO, a struct-API to stringently allow this only in special
|
||||
# cases?
|
||||
# -> better would be to have some way to (atomically) rewrite
|
||||
|
|
@ -159,13 +188,13 @@ async def open_registry(
|
|||
# next (set of) calls will apply whatever new one is passed
|
||||
# in.
|
||||
if was_set:
|
||||
Registry.addrs = None
|
||||
Registry.addrs = []
|
||||
|
||||
|
||||
@acm
|
||||
async def find_service(
|
||||
service_name: str,
|
||||
registry_addrs: list[tuple[str, int]] | None = None,
|
||||
registry_addrs: list[RegistryAddress] | None = None,
|
||||
|
||||
first_only: bool = True,
|
||||
|
||||
|
|
@ -175,7 +204,7 @@ async def find_service(
|
|||
| None
|
||||
):
|
||||
# try:
|
||||
reg_addrs: list[tuple[str, int|str]]
|
||||
reg_addrs: list[RegistryAddress]
|
||||
async with open_registry(
|
||||
addrs=(
|
||||
registry_addrs
|
||||
|
|
@ -219,7 +248,7 @@ async def find_service(
|
|||
|
||||
async def check_for_service(
|
||||
service_name: str,
|
||||
) -> None|tuple[str, int]:
|
||||
) -> None|RegistryAddress:
|
||||
'''
|
||||
Service daemon "liveness" predicate.
|
||||
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ pytest_plugins: tuple[str] = (
|
|||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--ll", action="store", dest='loglevel',
|
||||
default=None, help="logging level to set when testing")
|
||||
parser.addoption("--confdir", default=None,
|
||||
help="Use a practice API account")
|
||||
|
||||
|
|
@ -286,7 +284,12 @@ async def _open_test_pikerd(
|
|||
) as portal:
|
||||
raddr = portal.chan.raddr
|
||||
uw_raddr: tuple = raddr.unwrap()
|
||||
assert uw_raddr == reg_addr
|
||||
canonical_addr: tuple = (
|
||||
tractor.discovery._addr.wrap_address(
|
||||
reg_addr,
|
||||
).unwrap()
|
||||
)
|
||||
assert uw_raddr == canonical_addr
|
||||
yield (
|
||||
raddr._host,
|
||||
raddr._port,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from typing import (
|
|||
Callable,
|
||||
)
|
||||
from contextlib import asynccontextmanager as acm
|
||||
from types import SimpleNamespace
|
||||
|
||||
from exceptiongroup import BaseExceptionGroup
|
||||
import pytest
|
||||
|
|
@ -17,6 +18,7 @@ from piker.service import (
|
|||
find_service,
|
||||
Services,
|
||||
)
|
||||
from piker.service import _registry
|
||||
from piker.data import (
|
||||
open_feed,
|
||||
)
|
||||
|
|
@ -57,7 +59,12 @@ def test_runtime_boot(
|
|||
) as portal,
|
||||
):
|
||||
uw_raddr: tuple = pikerd_portal.chan.raddr.unwrap()
|
||||
assert uw_raddr == daemon_addr
|
||||
canonical_addr: tuple = (
|
||||
_registry._normalize_addresses(
|
||||
[daemon_addr],
|
||||
)[0]
|
||||
)
|
||||
assert uw_raddr == canonical_addr
|
||||
assert uw_raddr == portal.chan.raddr.unwrap()
|
||||
|
||||
# no service tasks should be started
|
||||
|
|
@ -66,6 +73,53 @@ def test_runtime_boot(
|
|||
trio.run(main)
|
||||
|
||||
|
||||
def test_registry_normalizes_tagged_addresses(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
'''
|
||||
Tractor now emits tagged registry addresses while existing Piker
|
||||
callers still pass legacy socket pairs. Direct list and set
|
||||
comparisons treated the two forms as different and retained the
|
||||
legacy value in `Registry.addrs`.
|
||||
|
||||
Seed `Registry.addrs` with a legacy pair, request the equivalent
|
||||
tagged address, and enter `open_registry()` as a root actor. The
|
||||
yielded and stored values must both use tractor's canonical form.
|
||||
|
||||
'''
|
||||
legacy = ('127.0.0.1', 6116)
|
||||
tagged = ('tcp', '127.0.0.1', 6116)
|
||||
actor = SimpleNamespace(
|
||||
aid=SimpleNamespace(uid=('test', 'uid')),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
tractor,
|
||||
'current_actor',
|
||||
lambda: actor,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
tractor,
|
||||
'is_root_process',
|
||||
lambda: True,
|
||||
)
|
||||
_registry.Registry.addrs = [legacy]
|
||||
|
||||
async def main():
|
||||
async with _registry.open_registry(
|
||||
[tagged],
|
||||
) as addrs:
|
||||
assert addrs == [tagged]
|
||||
assert _registry.Registry.addrs == [tagged]
|
||||
assert _registry._tractor_kwargs[
|
||||
'registry_addrs'
|
||||
] == [tagged]
|
||||
|
||||
try:
|
||||
trio.run(main)
|
||||
finally:
|
||||
_registry.Registry.addrs = []
|
||||
|
||||
|
||||
def test_datad_spawn(
|
||||
open_test_pikerd: AsyncContextManager,
|
||||
loglevel: str,
|
||||
|
|
|
|||
Loading…
Reference in New Issue