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,
|
_tractor_kwargs,
|
||||||
_default_reg_addr,
|
_default_reg_addr,
|
||||||
open_registry,
|
open_registry,
|
||||||
|
RegistryAddress,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -58,7 +59,7 @@ def get_runtime_vars() -> dict[str, Any]:
|
||||||
@acm
|
@acm
|
||||||
async def open_piker_runtime(
|
async def open_piker_runtime(
|
||||||
name: str,
|
name: str,
|
||||||
registry_addrs: list[tuple[str, int]] = [],
|
registry_addrs: list[RegistryAddress] = [],
|
||||||
tpt_bind_addrs: list|None = None,
|
tpt_bind_addrs: list|None = None,
|
||||||
|
|
||||||
enable_modules: list[str] = [],
|
enable_modules: list[str] = [],
|
||||||
|
|
@ -77,7 +78,7 @@ async def open_piker_runtime(
|
||||||
|
|
||||||
) -> tuple[
|
) -> tuple[
|
||||||
tractor.Actor,
|
tractor.Actor,
|
||||||
list[tuple[str, int]],
|
list[RegistryAddress],
|
||||||
]:
|
]:
|
||||||
'''
|
'''
|
||||||
Start a piker actor who's runtime will automatically sync with
|
Start a piker actor who's runtime will automatically sync with
|
||||||
|
|
@ -168,7 +169,7 @@ _root_modules: list[str] = [
|
||||||
|
|
||||||
@acm
|
@acm
|
||||||
async def open_pikerd(
|
async def open_pikerd(
|
||||||
registry_addrs: list[tuple[str, int]],
|
registry_addrs: list[RegistryAddress],
|
||||||
tpt_bind_addrs: list|None = None,
|
tpt_bind_addrs: list|None = None,
|
||||||
loglevel: str|None = None,
|
loglevel: str|None = None,
|
||||||
|
|
||||||
|
|
@ -272,7 +273,7 @@ async def open_pikerd(
|
||||||
|
|
||||||
@acm
|
@acm
|
||||||
async def maybe_open_pikerd(
|
async def maybe_open_pikerd(
|
||||||
registry_addrs: list[tuple[str, int]] | None = None,
|
registry_addrs: list[RegistryAddress] | None = None,
|
||||||
|
|
||||||
loglevel: str | None = None,
|
loglevel: str | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
|
@ -306,7 +307,7 @@ async def maybe_open_pikerd(
|
||||||
# async with open_portal(chan) as arb_portal:
|
# async with open_portal(chan) as arb_portal:
|
||||||
# yield arb_portal
|
# yield arb_portal
|
||||||
|
|
||||||
registry_addrs: list[tuple[str, int]] = (
|
registry_addrs: list[RegistryAddress] = (
|
||||||
registry_addrs
|
registry_addrs
|
||||||
or
|
or
|
||||||
[_default_reg_addr]
|
[_default_reg_addr]
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ from contextlib import (
|
||||||
)
|
)
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
|
TypeAlias,
|
||||||
)
|
)
|
||||||
|
|
||||||
import tractor
|
import tractor
|
||||||
|
|
@ -32,11 +33,23 @@ from tractor import (
|
||||||
Actor,
|
Actor,
|
||||||
Portal,
|
Portal,
|
||||||
)
|
)
|
||||||
|
from tractor.discovery._addr import (
|
||||||
|
LegacyUnwrappedAddress,
|
||||||
|
UnwrappedAddress,
|
||||||
|
wrap_address,
|
||||||
|
)
|
||||||
|
|
||||||
from piker.log import get_logger
|
from piker.log import get_logger
|
||||||
|
|
||||||
log = get_logger(name=__name__)
|
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?
|
# TODO? default path-space for UDS registry?
|
||||||
# [ ] needs to be Xplatform tho!
|
# [ ] needs to be Xplatform tho!
|
||||||
# _default_registry_path: Path = (
|
# _default_registry_path: Path = (
|
||||||
|
|
@ -46,10 +59,7 @@ log = get_logger(name=__name__)
|
||||||
|
|
||||||
_default_registry_host: str = '127.0.0.1'
|
_default_registry_host: str = '127.0.0.1'
|
||||||
_default_registry_port: int = 6116
|
_default_registry_port: int = 6116
|
||||||
_default_reg_addr: tuple[
|
_default_reg_addr: RegistryAddress = (
|
||||||
str,
|
|
||||||
int, # |str TODO, once we support UDS, see above.
|
|
||||||
] = (
|
|
||||||
_default_registry_host,
|
_default_registry_host,
|
||||||
_default_registry_port,
|
_default_registry_port,
|
||||||
)
|
)
|
||||||
|
|
@ -63,24 +73,37 @@ _registry: Registry | None = None
|
||||||
class Registry:
|
class Registry:
|
||||||
# TODO: should this be a set or should we complain
|
# TODO: should this be a set or should we complain
|
||||||
# on duplicates?
|
# on duplicates?
|
||||||
addrs: list[tuple[str, int]] = []
|
addrs: list[RegistryAddress] = []
|
||||||
|
|
||||||
# TODO: table of uids to sockaddrs
|
# TODO: table of uids to sockaddrs
|
||||||
peers: dict[
|
peers: dict[
|
||||||
tuple[str, str],
|
tuple[str, str],
|
||||||
tuple[str, int],
|
RegistryAddress,
|
||||||
] = {}
|
] = {}
|
||||||
|
|
||||||
|
|
||||||
_tractor_kwargs: dict[str, Any] = {}
|
_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
|
@acm
|
||||||
async def open_registry(
|
async def open_registry(
|
||||||
addrs: list[tuple[str, int]],
|
addrs: list[RegistryAddress],
|
||||||
ensure_exists: bool = True,
|
ensure_exists: bool = True,
|
||||||
|
|
||||||
) -> list[tuple[str, int]]:
|
) -> list[RegistryAddress]:
|
||||||
'''
|
'''
|
||||||
Open the service-actor-discovery registry by returning a set of
|
Open the service-actor-discovery registry by returning a set of
|
||||||
tranport socket-addrs to registrar actors which may be
|
tranport socket-addrs to registrar actors which may be
|
||||||
|
|
@ -92,9 +115,12 @@ async def open_registry(
|
||||||
actor: Actor = tractor.current_actor()
|
actor: Actor = tractor.current_actor()
|
||||||
aid: msg.Aid = actor.aid
|
aid: msg.Aid = actor.aid
|
||||||
uid: tuple[str, str] = aid.uid
|
uid: tuple[str, str] = aid.uid
|
||||||
preset_reg_addrs: list[
|
addrs = _normalize_addresses(addrs)
|
||||||
tuple[str, int]
|
preset_reg_addrs: list[RegistryAddress] = (
|
||||||
] = Registry.addrs
|
_normalize_addresses(Registry.addrs)
|
||||||
|
)
|
||||||
|
if preset_reg_addrs:
|
||||||
|
Registry.addrs = preset_reg_addrs
|
||||||
if (
|
if (
|
||||||
preset_reg_addrs
|
preset_reg_addrs
|
||||||
and
|
and
|
||||||
|
|
@ -102,9 +128,10 @@ async def open_registry(
|
||||||
):
|
):
|
||||||
if preset_reg_addrs != addrs:
|
if preset_reg_addrs != addrs:
|
||||||
# if any(addr in preset_reg_addrs for addr in addrs):
|
# if any(addr in preset_reg_addrs for addr in addrs):
|
||||||
diff: set[
|
diff: set[RegistryAddress] = (
|
||||||
tuple[str, int]
|
set(preset_reg_addrs)
|
||||||
] = set(preset_reg_addrs) - set(addrs)
|
-set(addrs)
|
||||||
|
)
|
||||||
if diff:
|
if diff:
|
||||||
log.warning(
|
log.warning(
|
||||||
f'`{uid}` requested only subset of registrars: {addrs}\n'
|
f'`{uid}` requested only subset of registrars: {addrs}\n'
|
||||||
|
|
@ -123,7 +150,9 @@ async def open_registry(
|
||||||
and
|
and
|
||||||
not Registry.addrs
|
not Registry.addrs
|
||||||
):
|
):
|
||||||
Registry.addrs.extend(actor.reg_addrs)
|
Registry.addrs.extend(
|
||||||
|
_normalize_addresses(actor.reg_addrs)
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
ensure_exists
|
ensure_exists
|
||||||
|
|
@ -139,13 +168,13 @@ async def open_registry(
|
||||||
Registry.addrs = (
|
Registry.addrs = (
|
||||||
addrs
|
addrs
|
||||||
or
|
or
|
||||||
[_default_reg_addr]
|
_normalize_addresses([_default_reg_addr])
|
||||||
)
|
)
|
||||||
|
|
||||||
# NOTE: only spot this seems currently used is inside
|
# NOTE: only spot this seems currently used is inside
|
||||||
# `.ui._exec` which is the (eventual qtloops) bootstrapping
|
# `.ui._exec` which is the (eventual qtloops) bootstrapping
|
||||||
# with guest mode.
|
# 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
|
# !TODO, a struct-API to stringently allow this only in special
|
||||||
# cases?
|
# cases?
|
||||||
# -> better would be to have some way to (atomically) rewrite
|
# -> 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
|
# next (set of) calls will apply whatever new one is passed
|
||||||
# in.
|
# in.
|
||||||
if was_set:
|
if was_set:
|
||||||
Registry.addrs = None
|
Registry.addrs = []
|
||||||
|
|
||||||
|
|
||||||
@acm
|
@acm
|
||||||
async def find_service(
|
async def find_service(
|
||||||
service_name: str,
|
service_name: str,
|
||||||
registry_addrs: list[tuple[str, int]] | None = None,
|
registry_addrs: list[RegistryAddress] | None = None,
|
||||||
|
|
||||||
first_only: bool = True,
|
first_only: bool = True,
|
||||||
|
|
||||||
|
|
@ -175,7 +204,7 @@ async def find_service(
|
||||||
| None
|
| None
|
||||||
):
|
):
|
||||||
# try:
|
# try:
|
||||||
reg_addrs: list[tuple[str, int|str]]
|
reg_addrs: list[RegistryAddress]
|
||||||
async with open_registry(
|
async with open_registry(
|
||||||
addrs=(
|
addrs=(
|
||||||
registry_addrs
|
registry_addrs
|
||||||
|
|
@ -219,7 +248,7 @@ async def find_service(
|
||||||
|
|
||||||
async def check_for_service(
|
async def check_for_service(
|
||||||
service_name: str,
|
service_name: str,
|
||||||
) -> None|tuple[str, int]:
|
) -> None|RegistryAddress:
|
||||||
'''
|
'''
|
||||||
Service daemon "liveness" predicate.
|
Service daemon "liveness" predicate.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,6 @@ pytest_plugins: tuple[str] = (
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
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,
|
parser.addoption("--confdir", default=None,
|
||||||
help="Use a practice API account")
|
help="Use a practice API account")
|
||||||
|
|
||||||
|
|
@ -286,7 +284,12 @@ async def _open_test_pikerd(
|
||||||
) as portal:
|
) as portal:
|
||||||
raddr = portal.chan.raddr
|
raddr = portal.chan.raddr
|
||||||
uw_raddr: tuple = raddr.unwrap()
|
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 (
|
yield (
|
||||||
raddr._host,
|
raddr._host,
|
||||||
raddr._port,
|
raddr._port,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from typing import (
|
||||||
Callable,
|
Callable,
|
||||||
)
|
)
|
||||||
from contextlib import asynccontextmanager as acm
|
from contextlib import asynccontextmanager as acm
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
from exceptiongroup import BaseExceptionGroup
|
from exceptiongroup import BaseExceptionGroup
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -17,6 +18,7 @@ from piker.service import (
|
||||||
find_service,
|
find_service,
|
||||||
Services,
|
Services,
|
||||||
)
|
)
|
||||||
|
from piker.service import _registry
|
||||||
from piker.data import (
|
from piker.data import (
|
||||||
open_feed,
|
open_feed,
|
||||||
)
|
)
|
||||||
|
|
@ -57,7 +59,12 @@ def test_runtime_boot(
|
||||||
) as portal,
|
) as portal,
|
||||||
):
|
):
|
||||||
uw_raddr: tuple = pikerd_portal.chan.raddr.unwrap()
|
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()
|
assert uw_raddr == portal.chan.raddr.unwrap()
|
||||||
|
|
||||||
# no service tasks should be started
|
# no service tasks should be started
|
||||||
|
|
@ -66,6 +73,53 @@ def test_runtime_boot(
|
||||||
trio.run(main)
|
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(
|
def test_datad_spawn(
|
||||||
open_test_pikerd: AsyncContextManager,
|
open_test_pikerd: AsyncContextManager,
|
||||||
loglevel: str,
|
loglevel: str,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue