You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(orchestration): a post-sync action must say what change requires it #17454
The bar says an action is needed and never what needs it. Owner's words:
it just displays there is package update, not saying what is needing this update and how to do it
PostSyncAction (autobot-slm-backend/api/roles.py:108-116) carries role_name, display_name, category, label, command, systemd_service. There is no field for the change
that triggered the action, and _classify_post_sync(role) takes only the role — it never sees what
the sync changed. So Install deps (AI Stack) cannot say "because requirements-ai.txt changed",
because at the point the action is built that fact is not in scope.
The information exists: sync_orchestrator knows which paths it synced for the role.
What "how to do it" needs
command is already carried and shown as the button's title, so the mechanism is one hover
away. What is missing is the cause, and the two are different questions: a reader who sees venv/bin/pip install -r ... still cannot tell whether it is needed because a requirement changed,
because a migration is pending, or because the button is always shown.
Shape to decide
a trigger field (changed path, or requirement name, or a short reason string) — the minimum;
who fills it: the classifier cannot, so either the endpoint gains the sync's change set, or the
action is built where that set is known;
The owner's complaint is literally true at every layer, and the narrowest point is one bit wide
Traced the whole chain before writing any code, because the shape question in the body ("who fills
it") turned out to have an answer that changes this issue's blockers. All line numbers are origin/main; neither file below is touched by #17456.
The signal is computed with the filename in hand and then collapsed to a boolean.
autobot-slm-backend/api/code_sync.py:4552 — the watched set is known and small: _DEPS_FILES = ["requirements.txt", "requirements-ai.txt", "package-lock.json"]
_check_deps_changed(code_source_dir, deployed_dir, dep_file) -> bool compares hashes per
file, so at that moment the specific file is in scope.
autobot-slm-backend/api/code_sync.py:4699 — _compute_deps_changed(component) -> bool loops
over that set and return True on the first hit. The filename is discarded here. Note it also stops looking, so even widened to a list it could not report the second and third file
without losing the early exit.
autobot-slm-backend/models/database.py:390 — the durable record is deps_changed = Column(Boolean, default=False). One bit per component sync job.
autobot-slm-backend/api/roles.py:111-119 — PostSyncAction has no trigger field, and _classify_post_sync(role) receives only the Role.
So the owner's sentence — "it just displays there is package update, not saying what is needing
this update" — is not a UI gap. deps_changedis the string "there is a package update",
stored at the narrowest width a fact can have. The frontend renders faithfully what the backend
chose to remember.
Consequence for this issue's blockers
The declared Depends on: #17453 is real but incidental — #17453 only orders the bar. AC1 is
blocked on #17455, which owns the durable record. Nothing in the system currently persists which
file changed, so there is no source an endpoint could read:
fleet_sync_node_states carries no changed-path field at all.
Role.source_paths exists, but it is the role's static sync scope, not what a given sync
changed. Populating trigger from it would satisfy the field's type and not its meaning — a
plausible-looking value in the right shape, which is the failure mode this repo treats as more
serious than an empty one.
What this implies for AC3, and a third state the body did not anticipate
AC3 asks that an unknown trigger be visibly distinct from one with a trigger. Once the data source
is deps_changed, there are three states, not two:
The middle row is the one that matters and it is the current production state for every action.
Rendering it as either of the outer two would be wrong in opposite directions: as a named trigger
it fabricates, as nothing-recorded it hides a fact the system does have. So AC3 needs three
renderings, and default=False means absent and false are indistinguishable in the existing column
— a widening of this field cannot be a boolean with a nullable sibling without carrying that
ambiguity forward.
Scope, stated rather than assumed
Steps 3-4 are code_sync.py and models/database.py — backend/orchestrator, a different file set
from #17456 (roles.py, _post_sync_plan.py, PostSyncActionBadges.vue). Under same file ⇒ one
PR, one agent the widening does not belong in #17456, and it is outside the frontend scope this
session is holding. AC2/AC3/AC4 are frontend and are mine, but they are not worth building against
a field that is always the middle row — a badge that says "something changed" is the defect
restated in a tooltip.
Recommendation: #17455 widens the record (returning and persisting the changed dep files, keeping
absent distinguishable from none), and this issue lands after it. Not filing that as new work — #17455 already owns it; adding the blocked_by edge instead.
Activity
mrveiss commentedon Sep 25, 2026
The owner's complaint is literally true at every layer, and the narrowest point is one bit wide
Traced the whole chain before writing any code, because the shape question in the body ("who fills
it") turned out to have an answer that changes this issue's blockers. All line numbers are
origin/main; neither file below is touched by #17456.The signal is computed with the filename in hand and then collapsed to a boolean.
autobot-slm-backend/api/code_sync.py:4552— the watched set is known and small:_DEPS_FILES = ["requirements.txt", "requirements-ai.txt", "package-lock.json"]_check_deps_changed(code_source_dir, deployed_dir, dep_file) -> boolcompares hashes perfile, so at that moment the specific file is in scope.
autobot-slm-backend/api/code_sync.py:4699—_compute_deps_changed(component) -> boolloopsover that set and
return Trueon the first hit. The filename is discarded here. Note it alsostops looking, so even widened to a list it could not report the second and third file
without losing the early exit.
autobot-slm-backend/models/database.py:390— the durable record isdeps_changed = Column(Boolean, default=False). One bit per component sync job.autobot-slm-backend/api/roles.py:111-119—PostSyncActionhas no trigger field, and_classify_post_sync(role)receives only theRole.So the owner's sentence — "it just displays there is package update, not saying what is needing
this update" — is not a UI gap.
deps_changedis the string "there is a package update",stored at the narrowest width a fact can have. The frontend renders faithfully what the backend
chose to remember.
Consequence for this issue's blockers
The declared
Depends on: #17453is real but incidental — #17453 only orders the bar. AC1 isblocked on #17455, which owns the durable record. Nothing in the system currently persists which
file changed, so there is no source an endpoint could read:
component_sync_jobscarriesdeps_changed(bool),post_steps(newline-joined log),component,status,success,message.fleet_sync_node_statescarries no changed-path field at all.Role.source_pathsexists, but it is the role's static sync scope, not what a given syncchanged. Populating
triggerfrom it would satisfy the field's type and not its meaning — aplausible-looking value in the right shape, which is the failure mode this repo treats as more
serious than an empty one.
What this implies for AC3, and a third state the body did not anticipate
AC3 asks that an unknown trigger be visibly distinct from one with a trigger. Once the data source
is
deps_changed, there are three states, not two:requirements-ai.txtchanged"deps_changed=True, file discardeddefault=Falsenever writtenThe middle row is the one that matters and it is the current production state for every action.
Rendering it as either of the outer two would be wrong in opposite directions: as a named trigger
it fabricates, as nothing-recorded it hides a fact the system does have. So AC3 needs three
renderings, and
default=Falsemeans absent and false are indistinguishable in the existing column— a widening of this field cannot be a boolean with a nullable sibling without carrying that
ambiguity forward.
Scope, stated rather than assumed
Steps 3-4 are
code_sync.pyandmodels/database.py— backend/orchestrator, a different file setfrom #17456 (
roles.py,_post_sync_plan.py,PostSyncActionBadges.vue). Under same file ⇒ onePR, one agent the widening does not belong in #17456, and it is outside the frontend scope this
session is holding. AC2/AC3/AC4 are frontend and are mine, but they are not worth building against
a field that is always the middle row — a badge that says "something changed" is the defect
restated in a tooltip.
Recommendation: #17455 widens the record (returning and persisting the changed dep files, keeping
absent distinguishable from none), and this issue lands after it. Not filing that as new work —
#17455 already owns it; adding the
blocked_byedge instead.fix(orchestration): order post-sync actions and restart each unit once (