From a93cbe76f98895d9794c56303b78955f30a8c46f Mon Sep 17 00:00:00 2001 From: anti Date: Tue, 21 Apr 2026 19:56:58 -0400 Subject: [PATCH] feat(mutator): update_decky payload accepts top-level services list apply_update_decky only merged payload.patch into decky_config. Since services is a separate DB column, there was no way to replace a decky's services list via a mutation. Add a top-level services key to the op payload that maps straight onto the services column. Unblocks the MazeNET editor routing service-add/service-drop actions through the mutation queue on active topologies. --- decnet/mutator/ops.py | 3 +++ tests/topology/test_mutator.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/decnet/mutator/ops.py b/decnet/mutator/ops.py index e42a77a9..013273a3 100644 --- a/decnet/mutator/ops.py +++ b/decnet/mutator/ops.py @@ -280,6 +280,7 @@ async def apply_update_decky( ``payload`` keys: ``decky`` — decky name. ``patch`` — dict merged into existing ``decky_config``. + ``services`` — replacement top-level services list. ``x``,``y`` — layout coords. """ hydrated = await _hydrated(repo, topology_id) @@ -291,6 +292,8 @@ async def apply_update_decky( merged = dict(decky["decky_config"]) merged.update(payload["patch"]) patch["decky_config"] = merged + if "services" in payload: + patch["services"] = list(payload["services"]) for key in ("x", "y"): if key in payload: patch[key] = payload[key] diff --git a/tests/topology/test_mutator.py b/tests/topology/test_mutator.py index 65508b2c..87ca7b26 100644 --- a/tests/topology/test_mutator.py +++ b/tests/topology/test_mutator.py @@ -158,6 +158,25 @@ async def test_apply_add_lan_persists(repo): assert "LAN-MUT" in names +@pytest.mark.anyio +async def test_apply_update_decky_replaces_services(repo): + """Top-level ``services`` payload key replaces the decky's services list.""" + tid = await _make_active(repo) + decky = (await repo.list_topology_deckies(tid))[0] + await apply_update_decky( + repo, tid, + { + "decky": decky["decky_config"]["name"], + "services": ["ssh", "http"], + }, + ) + updated = next( + d for d in await repo.list_topology_deckies(tid) + if d["uuid"] == decky["uuid"] + ) + assert sorted(updated["services"]) == ["http", "ssh"] + + @pytest.mark.anyio async def test_apply_rejected_on_validator_error(repo): """Unknown service name must trip the post-apply validator."""