diff --git a/ai/prompt-io/opencode/20260820T163519Z_5f631e19_prompt_io.md b/ai/prompt-io/opencode/20260820T163519Z_5f631e19_prompt_io.md new file mode 100644 index 00000000..8eccac77 --- /dev/null +++ b/ai/prompt-io/opencode/20260820T163519Z_5f631e19_prompt_io.md @@ -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. diff --git a/ai/prompt-io/opencode/20260820T163519Z_5f631e19_prompt_io.raw.md b/ai/prompt-io/opencode/20260820T163519Z_5f631e19_prompt_io.raw.md new file mode 100644 index 00000000..526f58ae --- /dev/null +++ b/ai/prompt-io/opencode/20260820T163519Z_5f631e19_prompt_io.raw.md @@ -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. diff --git a/piker/service/_actor_runtime.py b/piker/service/_actor_runtime.py index 6c09a9e7..874616ee 100644 --- a/piker/service/_actor_runtime.py +++ b/piker/service/_actor_runtime.py @@ -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] diff --git a/piker/service/_registry.py b/piker/service/_registry.py index 2d787d7c..12206389 100644 --- a/piker/service/_registry.py +++ b/piker/service/_registry.py @@ -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. diff --git a/tests/conftest.py b/tests/conftest.py index ad7ecce4..cd7a3b33 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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, diff --git a/tests/test_services.py b/tests/test_services.py index 345bc017..ad164906 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -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,