Repository navigation
fix(orchestration): order post-sync actions and restart each unit once #17453
Description
Parent: #17452
Problem
get_node_actions (autobot-slm-backend/api/roles.py:576) builds a node's post-sync list
role-major — walk the roles, extend per role — so the order is role iteration order and two
things go wrong.
A restart can precede the install it depends on. On the reported bar, Restart autobot-celery
was at position 9 while Install deps (Shared Library) was at 13 and Run migrations (Backend API)
at 15.
The same systemd unit is scheduled twice, in two ways:
ollamatwice with identical labels — owned by bothautobot-llm-cpuandautobot-llm-gpu
(SLM role shape has two sources of truth: role_registry.DEFAULT_ROLES vs autobot-infrastructure/*/manifest.yml #16025), one restart action from each role.autobot-celerytwice with different labels — once alone, once inside
Restart autobot-backend autobot-celery. Deduplication must be at the unit level; comparing
labels, commands or(role, category)keys cannot see this one.
Fix
autobot-slm-backend/api/_post_sync_plan.py, called from get_node_actions:
- order
install→schema→build→restart, stable within a category; - an unknown category sorts into the middle rather than after the restarts, so a category added to
_classify_post_syncand not to the rank table degrades safely; - every unit scheduled at most once, taking larger restart groups first — a role grouping
autobot-backendwithautobot-celerymeans they come back together, so the group survives whole
and the standalone subset is dropped rather than the group being shrunk.
Separate module because roles.py is at its size ratchet pin (691, shrink-only).
PostSyncActionBadges.vue numbers the badges — a numeral, not a word, so it adds no string to
eleven locales — and its docstring records that the order is the server's and must not be re-sorted
in the view.
Acceptance criteria
- No
restartaction appears before anyinstall,schemaorbuildaction. - No systemd unit appears in more than one action of a node's plan.
- A restart group that contains another action's units entirely survives; the subset is dropped.
- A partial overlap keeps the unscheduled remainder and its label matches what it restarts.
- An unknown category sorts before the restarts.
- Ordering is stable within a category, so output changes only when roles do.
- A restart action with no unit is kept, not swallowed — dropping it would hide a misconfigured
role behind a tidy list. - A plan with no duplicates is returned unchanged (the control that stops the above being
satisfied by dropping restarts). - The view does not sort, filter or group the actions itself.
Activity
mrveiss commentedon Sep 25, 2026
Delivered by PR #17456, merged to
mainas4333c47fb0. Every criterion verified against mergedcode read back from base (
git show origin/main:<path>), not against the PR diff.origin/mainrestartbeforeinstall/schema/build_post_sync_plan.py:48_CATEGORY_RANK = {"install": 0, "schema": 1, "build": 2, "restart": 4}, applied at:113sorted(others, key=_rank)with restarts appended after. Test:test_a_restart_never_precedes_an_install,test_every_category_lands_in_dependency_order_without_duplicate_units()at:63. Tests:test_two_roles_owning_one_unit_produce_one_restart,test_a_unit_inside_a_group_is_not_restarted_alone_as_well:78sorted(restarts, key=lambda a: -len(_units(a)))— largest group first, so a subset finds its units already scheduled. Test:test_the_intended_group_survives_and_the_subset_is_droppedtest_a_partial_overlap_keeps_the_units_not_already_scheduled:49_UNKNOWN_CATEGORY_RANK = 3against restart's 4, resolved at:60via.get(..., _UNKNOWN_CATEGORY_RANK). Test:test_an_unknown_category_lands_before_the_restartssortedis stable and_rankis the sole key. Test:test_two_roles_in_one_category_keep_their_incoming_ordertest_a_restart_with_no_unit_is_kept_rather_than_swallowedtest_a_plan_with_no_duplicates_is_returned_unchanged— the control that stops the dedupe criteria being satisfied by dropping restarts wholesalePostSyncActionBadges.vueonmaincontains no.sort(,.filter(,groupBy,reduce(orcomputed(; it rendersv-for="(action, index) in actions"at:104and numbers steps with{{ index + 1 }}at:121, so order is the server'sTestTheLiveBarThatPromptedThisreplays the 22-button bar from the original report and asserts thetwo redundant restarts are gone, nothing restarts before the installs and the migration, and no
action is lost.
What this issue does NOT deliver, so it is not read as covering more than it does. The ordering
is fixed on the manual surface only. The automated sync path still runs post-sync work per role
(
_sync_nodes_for_role), so a unit owned by two roles still restarts twice there — that asymmetry is#17455, and it is the reason #17455 asks for the same policy to be imported rather than
reimplemented. Two of the owner's four original complaints (attribution, and being part of the
code-sync process and its logs) remain open as #17454 and #17455.
Closing: all nine criteria met with merged-code evidence.