Files
sencho/docs/features/node-compatibility.mdx
T
Anso 92d974b13e fix: condition --volumes in downStack() on the removeVolumes option (#1764)
* fix: condition --volumes in downStack() on the removeVolumes option

ComposeService.downStack() hardcoded --volumes on every stack delete,
ignoring the "Also remove associated volumes" checkbox and destroying
volumes the operator asked to keep. The sibling Take-down path (runDown)
already conditions --volumes correctly.

- Add options?: { removeVolumes?: boolean } to downStack()
- Default to data-preserving (no --volumes when option absent)
- DeletedStackDeletionService reads the persisted intent flag
- Templates rollback passes removeVolumes: true (clean up failed deploy)
- Blueprint withdraw passes removeVolumes: false (volumes preserved)

* docs: update Delete row to reflect conditional volume removal

The Delete row now describes that volumes are removed only when the
operator opts in, matching the behavior introduced by the downStack fix.

* fix: add capability gate for delete pruneVolumes and fix QA findings

Four P0 issues found in live QA:

P0-1/P0-4 - No capability gate on delete's pruneVolumes:
  Add stack-delete-prune-volumes capability so the frontend hides the
  "Also remove associated volumes" checkbox on nodes that don't support
  conditional volume removal on delete. Without this, an operator on an
  old node sees a VOLUMES KEPT promise the old node silently breaks.
  Frontend-only gate: no API or proxy gate because the old node's
  fallback (always destroy) is correct for the checked case.

P0-2 - Checkbox state leaked across dialogs:
  Reset pruneVolumes in onConfirm before calling the parent, so a
  previously checked box doesn't appear pre-checked when the dialog
  opens for a different stack.

P0-3 - Delete not bound to the active node:
  Capture activeNode.id at delete time and pass it as an explicit
  nodeId to apiFetch, matching the Take Down pattern. Without this,
  switching the active node while the dialog is open silently deletes
  the wrong stack on the wrong node.

* fix: update test assertions for nodeId binding and showVolumeOption gate

P0-3 added nodeId to apiFetch DELETE calls — two useStackActions tests
now expect the parameter. P0-1 gated the volume checkbox behind
showVolumeOption — the confirming test now passes the prop.

* fix: gate volume hint on showVolumeOption to prevent false promise

On nodes without stack-delete-prune-volumes, volumes are always
destroyed. Showing VOLUMES KEPT was a lie. Now the hint is hidden
entirely when the capability is absent.

* fix: gate delete against nodes that cannot guarantee volume preservation

Hiding the checkbox and the misleading hint stopped the false promise but
not the data loss: an unchecked delete against a node lacking
stack-delete-prune-volumes still reached that node and its downStack()
still destroyed volumes unconditionally, now with no warning at all.

- remoteNodeProxy.ts: block an unacknowledged DELETE /stacks/:name
  (no pruneVolumes=true) to a remote lacking the capability, mirroring
  the existing removeVolumes gate on the down route. An explicit
  pruneVolumes=true always proxies through since that matches what an
  unsupported remote does anyway.
- DeleteStackDialog: rework around a three-state model (supported /
  unsupported / unknown) instead of a boolean. A node whose capabilities
  have not been confirmed (meta not yet fetched, or a failed probe) is
  now treated like a supported node, not forced onto the destructive
  path just because its state is unresolved.
- Fix deleteStack's error toast, which surfaced the raw JSON response
  body instead of the parsed error message.
- Fix CreateStackDialog's orphan-stack rollback (docker-run import),
  which silently no-op'd against a node requiring acknowledgement.
- Update node-compatibility.mdx and stack-management.mdx to describe
  the new gate.

* test: advertise stack-delete-prune-volumes on the scoped-evidence fixtures

These mock remotes simulate nodes capable enough to run scoped-stack-auth-evidence
RBAC and were pinned before stack-delete-prune-volumes existed, so the new delete
gate now blocked their unacknowledged DELETE calls before reaching the mock server,
failing the grant-tuple-cleanup assertions the tests actually check.
2026-08-04 13:04:21 -04:00

129 lines
10 KiB
Plaintext

---
title: Node Compatibility
description: How Sencho handles version differences across nodes and gracefully gates features that are not available on the active node.
---
When you manage [multiple nodes](/features/multi-node), each running its own Sencho instance, those instances may be on different versions. A node running an older release will not have features that shipped in a newer one. Sencho detects this automatically and gates each feature on whether the active node advertises support for it, with no errors and no broken pages.
## How it works
Every Sencho instance exposes a public `GET /api/meta` endpoint that advertises its version and a list of **capabilities**, one flag per feature it implements. When you switch to a node, your control instance fetches that node's metadata and caches it (3 minutes on the server, and 5 minutes on success or 30 seconds on failure in the dashboard), so the version pill and the per-feature gates stay in sync without re-hitting the remote on every navigation. Testing a node from **Settings · Nodes**, or a node finishing an update, refreshes its metadata right away rather than waiting for the cache to expire.
Features whose capability is missing from the active node's list are replaced by a lock card naming the missing feature and prompting you to upgrade the node. Core features (stack management, containers, resources, logs) work on every Sencho version and are never gated this way.
<Note>
Capabilities are flag-based, not version-based. Sencho never compares version numbers; it checks whether the active node explicitly advertises support for each feature. This keeps the system forward-compatible as new capabilities are added.
</Note>
## What you will see
### Version in the node switcher
The node switcher popover shows each node's Sencho version next to its type chip, separated by a middle dot.
<Frame>
<img src="/images/node-compatibility/node-switcher-versions.png" alt="Node switcher popover listing four connected nodes. The Local row reads LOCAL · v0.95.0 with a star icon for the default node. The Opsix row reads REMOTE · v0.95.0. The Pitt-Moba and SLX-Mars rows read REMOTE only, with no version pill, because this dashboard session has not switched to them yet." />
</Frame>
A node's version pill appears once Sencho has fetched its metadata. The metadata cache populates the first time you visit a node, so a node you have not switched to yet in the current session shows its type chip alone, with no version pill, until you do. A node that does not return metadata at all shows no version pill on either visit.
### Lock card on unsupported features
When the active node does not advertise a capability that a feature needs, the feature panel is replaced by a centered lock card.
<Frame>
<img src="/images/node-compatibility/lock-card.png" alt="Capability lock card centered in the editor area. A circular icon badge holds an Unplug glyph at the top. Below it, a title reads Audit Log is not available on this node and a body line reads Local is running v0.95.0. Upgrade the node to use this feature." />
</Frame>
The card carries the feature name, the node name, and (when known) the running version, so you can see at a glance what is missing and where. When the node has not reported a version, the body line drops the version and reads "*node-name* does not advertise this capability. Upgrade the node to use this feature." instead. The gated panel itself does not load; the lock card replaces it, which keeps slow capability-heavy views from briefly flashing into view before being hidden.
### Connection test details
When you test a remote node from **Settings · Nodes**, the Connection Details panel that appears under the table lists the node's running Sencho version alongside its OS, architecture, container count, image count, and CPU count.
<Frame>
<img src="/images/node-compatibility/connection-test-version.png" alt="Connection Details panel for Opsix showing Instance: Remote Sencho, Sencho: v0.95.0, OS: Remote, Arch: Remote, Containers: 4, Images: 4, CPUs: 2, with a wifi-style icon in the heading." />
</Frame>
This is the most direct way to confirm what version a remote node is on without switching to it first.
## Capability list
Every Sencho release ships with a static list of capabilities. The current list is:
| Capability | Feature |
|-----------|---------|
| `stacks` | Stack management (always available) |
| `containers` | Container operations (always available) |
| `resources` | Resource browser (always available) |
| `templates` | App Store (always available) |
| `global-logs` | Global log viewer (always available) |
| `system-stats` | Host system statistics (always available) |
| `fleet` | Fleet management view |
| `auto-updates` | Auto-Update policies |
| `labels` | Stack labels |
| `webhooks` | Webhook triggers |
| `network-topology` | Network topology view |
| `notifications` | Alert notifications |
| `notification-routing` | Notification routing rules |
| `notification-suppression` | Mute rules |
| `host-console` | Host Console (legacy advertisement retained for mixed-version fleets) |
| `host-console-community` | Host Console without a paid-license requirement on this node |
| `container-exec` | Container exec terminal |
| `audit-log` | Audit log |
| `scheduled-ops` | Scheduled operations |
| `sso` | SSO authentication |
| `api-tokens` | API token management |
| `users` | User management |
| `registries` | Private registry management |
| `self-update` | Self-update from the dashboard |
| `vulnerability-scanning` | Image vulnerability scanning |
| `compose-doctor` | Compose Doctor preflight |
| `update-guard` | Pre-update rollback-readiness dialog |
| `compose-networking` | Stack Networking tab |
| `env-inventory` | Stack Environment tab (env var inventory) |
| `container-label-inventory` | Stack Compose Labels tab and Fleet label filters |
| `project-env-files` | Project-level env file selection in the Environment tab |
| `compose-storage` | Stack Storage tab (volume explorer) |
| `cross-node-rbac` | Role enforcement on requests forwarded to a remote node |
| `stack-down-remove-volumes` | The "also remove volumes" option on Take Down |
| `stack-delete-prune-volumes` | Preserve volumes on Delete unless the operator opts in to remove them |
| `guided-external-network-preflight` | Guided missing-external-network check before deploy |
| `service-scoped-update` | Per-service update, rebuild, and restore on multi-service stacks |
<Note>
`vulnerability-scanning` is advertised only when the Trivy binary is present on the node. If a node has no scanner installed, it omits this capability and the scanning UI is replaced by a lock card whose body reads "Install a scanner from the Scanner setup tab." instead of the generic upgrade message.
</Note>
<Note>
A node connected in [Pilot Agent](/features/multi-node#add-a-remote-node-pilot-agent) mode never advertises `host-console` or `host-console-community`, even after upgrading, because that feature's control-to-agent path is not yet wired through the enrollment tunnel. This is the one capability gap that upgrading the node cannot close; it only closes on a Distributed API Proxy connection.
</Note>
A handful of capabilities gate a smaller piece of behavior rather than a whole panel, so a missing one falls back to an older behavior instead of a lock card:
- Nodes that do not advertise `guided-external-network-preflight` keep legacy deploy behavior (no guided missing-external dialog). When the capability is present but the check cannot run, deploy fails closed instead of guessing.
- Nodes that do not advertise `service-scoped-update` fall back to the legacy per-container layout on multi-service stacks: no declared-service headers, and no per-service update, rebuild, or restore.
- Nodes that do not advertise `update-guard` run manual updates directly, with no pre-update rollback-readiness dialog.
- `cross-node-rbac` is a security boundary, not a convenience: the control instance refuses to forward a non-admin request, or a confirmed stop-by-label, to a remote node that does not advertise it. This stops a mixed-version fleet from letting a lower-privileged action escalate on an un-upgraded node.
- `stack-delete-prune-volumes` is also a data-safety boundary: a node that does not advertise it cannot guarantee volumes survive a Delete, so the confirmation dialog states that up front and forwards the delete with removal already acknowledged, and the control instance refuses to forward an *unacknowledged* delete to such a node at all rather than risk it silently destroying volumes the operator meant to keep.
## Handling nodes that do not advertise metadata
If a remote node does not respond to `/api/meta` (for example, an unreachable instance, a slow handshake, or one that does not implement the endpoint), Sencho falls back to an offline metadata record with no version and an empty capability list. In that state:
- The node's row in the switcher and the connection-test panel show no version pill.
- Every capability-gated feature on that node shows the lock card.
- Core features (stacks, containers, resources, logs) continue to work normally, with one exception: Delete requests preservation instead of guessing, so a genuinely un-upgraded remote returns an "upgrade this node" error rather than risk destroying volumes silently (see `stack-delete-prune-volumes` above).
- No other errors are surfaced in the UI; the control instance retries the metadata fetch after a short backoff.
If you expect a node to support a feature that is being gated, the fastest fix is to update that node to the latest Sencho release. See the [upgrade guide](/operations/upgrade) for instructions.
## Interaction with license tiers
Some features need both an Admiral license **and** node capability support. The two gates evaluate in this order:
1. The license gate is checked first. On the wrong tier, the feature's entry point (sidebar item, top-nav button, settings section) is hidden entirely, so you never reach the panel.
2. If you are on the right tier but the active node does not advertise the capability, the entry point is visible but the panel is replaced by the capability lock card.
The practical effect is that you only see the lock card for features your plan already covers, so it is always actionable: upgrading the node restores access to that feature.