refactor(auto-update): retire per-stack gate, drive auto-update from schedules only (#1233)

* refactor(auto-update): retire per-stack gate, drive auto-update from schedules only

The per-stack Auto-update toggle in the stack sidebar context menu wrote a
gate row to `stack_auto_update_settings`, but actual updates only ran when a
`scheduled_tasks` row with `action='update'` fired. On a fresh install the
toggle was inert: detection ran every 6h, nothing was applied.

The same context menu already exposes `Schedule task`, which opens
ScheduledOperationsView pre-filled for the stack where the user can pick
`Auto-update Stack` and any cron. Keeping the toggle alongside that flow
duplicated the same action and turned the gate table into a parallel store
of "is a covering schedule active" derivable from `scheduled_tasks` itself.

Drop the gate model entirely:
- Backend: remove the `stack_auto_update_settings` table and its four
  accessors, the three routes under /api/stacks/*/auto-update, the per-stack
  skip in /api/auto-update/execute and SchedulerService.executeUpdate's
  fleet branch, and the clearStackAutoUpdateSetting call on stack delete.
  Dashboard `autoUpdate` count derives from scheduled_tasks (action='update'
  rows pinned to the node, total/enabled split).
- Frontend: drop the Auto-update entry from the sidebar context menu and its
  optimistic toggle plumbing. Drop autoUpdateSettings state, the
  /stacks/auto-update-settings fetch, and the auto-update-settings-changed
  WebSocket branch. Slim useSidebarActivitySummary (just nextRunAt; no
  enabled/total counts). AutoUpdateReadinessView's per-card autoUpdateEnabled
  now means "a covering enabled action='update' schedule exists" (per-stack
  row or fleet row on this node, earliest next_run_at wins, per-stack row
  wins on ties), with the gate-fetch removed.
- New: scheduledTasksRouter broadcasts scope: 'scheduled-tasks' on POST,
  PUT, PATCH /toggle, and DELETE so useConfigurationStatus and
  useNextAutoUpdateRun refetch under the 250ms debounce instead of waiting
  for the 60s poll. The broadcast is wrapped so a broken subscriber socket
  cannot turn a successful mutation into a 500.
- Docs: rewrite the "Per-stack control" section of auto-update-policies.mdx
  to describe the schedule-based model; update the matching troubleshooting
  entry. The misleading fleet-update help text in ScheduledOperationsView
  is corrected to reflect that every stack on the node is covered.

Tier parity: the surviving auto-update path (Schedule task -> Auto-update
Stack / All Stacks) is gated `requirePaid + requireAdmin` backend and
`isPaid + isAdmin` frontend, matching the gate the deleted routes carried.
The pre-commit grep returns no tier-related diff outside this PR's scope.

No data migration is provided: greenfield rules apply, and the leftover
table on already-shipped instances is harmless because no code reads or
writes it after this PR.

* docs: sweep remaining references to the per-stack auto-update toggle

The previous commit retired the per-stack Auto-update gate in favor of
configuring auto-update purely through scheduled tasks. This commit
removes the now-stale mentions of that toggle across the operator docs:

- docs/features/sidebar.mdx: drop the Auto-update entry from the Inspect
  group description, the matching screenshot alt-text, and the Skipper
  Note that listed it. Schedule task now carries the cross-link to
  Auto-Update Policies.
- docs/features/stack-management.mdx: drop the Auto-update list item;
  refresh the Schedule task entry to mention the Auto-update Stack action.
- docs/features/dashboard.mdx: rename the Configuration Status row from
  "Auto-update stacks" to "Auto-update schedules" with the new value
  shape, and rewrite the troubleshooting accordion to describe the
  scheduled-tasks invalidation path.
- docs/features/scheduled-operations.mdx: rewrite the Auto-update All
  Stacks row and helper text to reflect that every stack on the node is
  covered (no per-stack opt-out from this surface anymore).
- docs/features/multi-node.mdx: rewrite the Updates column definition to
  derive the Auto/Off flag from enabled Auto-update Stack / Auto-update
  All Stacks schedules instead of the removed per-stack policy.

The auto-update-policies.mdx rewrite in the previous commit already
covered the main reference page. The sidebar-context-menu.png screenshot
will be refreshed on release once the new menu is live in production;
the alt text is updated in this commit so it accurately describes the
shipping state.

No website edits needed: the Auto-Update Policies feature card description
("Schedule automatic image pulls and redeployments per stack on your own
cadence") and the feature matrix labels ("Auto-update stack schedule",
"Auto-update all stacks schedule") remain accurate under the new model.

* fix(stacks): drop orphaned requireAdmin import after auto-update route removal

CI's backend lint step flagged this PR's earlier deletion of the three
/api/stacks/*/auto-update routes: those handlers were the only callers of
`requireAdmin` inside routes/stacks.ts, leaving the named import on line 15
unreferenced. `requirePaid` and `effectiveTier` from the same line are still
in use elsewhere in the file and stay.

tsc --noEmit does not flag unused named imports; ESLint's no-unused-vars
does. Local backend lint reproduces and now reports 0 errors against the
existing 334-warning baseline.
This commit is contained in:
Anso
2026-05-26 11:08:33 -04:00
committed by GitHub
parent 2a29fed117
commit adcd04b01a
36 changed files with 295 additions and 668 deletions
+2 -2
View File
@@ -98,7 +98,7 @@ The Automation block only renders on Skipper or Admiral.
| Row | What it shows |
|-----|---------------|
| **Auto-heal policies** | `<enabled> / <total> active` for crash-recovery policies across all stacks; reads `None` when no policies exist |
| **Auto-update stacks** | `<enabled> / <total>` count of stacks enrolled in automated image-update checks; reads `None` when none are enrolled |
| **Auto-update schedules** | `<enabled> / <total> active` count of `Auto-update Stack` / `Auto-update All Stacks` rows configured for this node; reads `None` when none are configured |
| **Webhooks** (Skipper) | Active inbound deploy webhooks tied to Git Sources or stacks, formatted `<n> actives` |
| **Scheduled tasks** (Admiral) | Active scheduled operations (backups, restarts, scripts), formatted `<n> actives` |
@@ -220,7 +220,7 @@ When you switch the active node from the node switcher, the dashboard resets eve
Pilot-agent nodes connect outbound to the primary over a reverse tunnel, so there is no synchronous request-response loop to measure. The Heartbeat card uses the tunnel's last heartbeat to set the dot color, and intentionally renders `n/a` in the latency column. If the dot is red, check the agent container's logs for the first `[Pilot]` line and confirm the agent can reach the primary URL it is dialing. See [Pilot agent](/features/pilot-agent).
</Accordion>
<Accordion title="Configuration Status still shows the old value after I changed a setting">
The card refreshes every 60 seconds on its own schedule. Toggling a stack's **Auto-update** setting also dispatches a live invalidation that the card picks up within a second. Other settings changes (cloud backup provider switch, SSO provider change, alert rule edit, agent toggle) update on the next 60-second tick. If the row stays stale after a minute, hard-reload the dashboard tab to force a fresh fetch.
The card refreshes every 60 seconds on its own schedule. Creating, editing, toggling, or deleting a scheduled task also dispatches a live invalidation that the card picks up within a second. Other settings changes (cloud backup provider switch, SSO provider change, alert rule edit, agent toggle) update on the next 60-second tick. If the row stays stale after a minute, hard-reload the dashboard tab to force a fresh fetch.
</Accordion>
<Accordion title="The masthead shows a 'metrics stale' chip next to the node name">
The chip appears after three consecutive failures of the live metrics endpoints (`/api/stats` or `/api/system/stats`), which usually means the Docker socket is unreachable from this Sencho instance or the metrics service has stopped. The dashboard keeps polling on every cycle; the chip describes the freshness of the visible numbers, not the polling cadence. The chip clears on the first successful response once both endpoints are within the threshold. Check the Docker daemon status on the active node first, then the Sencho container logs for `[Dashboard]` lines if the chip persists.