* fix(nodes): close capability-gating gaps in node compatibility
Vulnerability scanning is now gated correctly on whether the active node
advertises support for it:
- A node without the Trivy binary stops advertising the scanning capability.
Previously the capability was toggled only on a state change, so a node that
booted without Trivy kept advertising scanning it could not perform.
- The control node's own capability list now reflects features disabled at
runtime, matching what it advertises to peers.
- The scan history surface shows a clear "not available on this node" card,
with its header actions hidden, instead of attempting a request that fails.
A node's version and capability metadata now refreshes immediately after a
connection test or a completed update, rather than waiting out the cache.
Capability gates fail closed to the unavailable card when a node's metadata
request errors, instead of staying open until the next fetch.
Adds a test that fails if the frontend and backend capability lists drift,
plus coverage for the metadata error path, the runtime-disabled local meta,
the scanning capability sync, and the metadata cache invalidation paths.
* fix(nodes): refresh node metadata client-side after a connection test
A connection test dropped the server-side metadata cache, but the dashboard
kept its own cached copy until the client TTL expired, so version and
capability gates could stay stale in the browser. The test now forces a
client-side metadata refresh for that node, so the version pill and gates
reflect the node's current state immediately.
Also strips any URL userinfo before logging the metadata fetch target, and
makes the scanning-capability detection test deterministically exercise the
no-binary disable path rather than depending on whether the runner has Trivy.
Drops `requirePaid` from `POST /api/fleet/update-all` so Community admins
can dispatch bulk node updates. Per-node OTA was already Community-
reachable (admin-only); this completes the move so the full Remote OTA
surface ships at Community.
Frontend mirrors the backend: removes `canBulkUpdate` from
NodeUpdatesSheet so the "Update all (N)" affordance is purely data-
driven on `updatableRemoteCount > 0`.
Docs realigned to drop fence-spec and Skipper-only phrasing on the
Update all bulk action:
- features/licensing.mdx: Community line now lists Remote OTA (per-node
and Update all); Skipper Fleet Actions parenthetical drops "bulk
update all"
- features/remote-updates.mdx: Note rewritten to role-only requirement
- features/fleet-view.mdx: Update all (n) bullet drops the tier clause
- features/overview.mdx: Fleet View and Remote updates blurbs drop the
Skipper/Admiral fences
- operations/upgrade.mdx: Note rephrased without naming tiers
Test coverage:
- fleet.test.ts: tier-gating spec flipped to assert Community access
- fleet-pilot-update.test.ts: bulk-OTA dispatch suite now spies tier
to Community so it doubles as a regression guard
POST /api/fleet/nodes/:id/update and POST /api/fleet/update-all read
node.api_url + node.api_token directly, so a pilot-agent row (which
carries neither) returned "Remote node not configured." and was
filtered out of bulk update. Both routes now resolve the target via
NodeRegistry.getProxyTarget(), which returns the loopback URL for an
active pilot tunnel and the configured api_url for proxy-mode remotes.
fetchMetaForNode replaces the direct fetchRemoteMeta call for the
self-update capability check.
self-update is removed from PILOT_DISABLED_CAPABILITIES so a
Compose-deployed pilot can advertise it; the host-console filter
stays. SelfUpdateService still gates the local-end capability on the
container actually carrying docker-compose labels, so a docker-run
pilot self-disables and the Fleet UI sees a clean 503 instead of an
ambiguous failure.
Tests: new fleet-pilot-update covers the four single-node branches
(success via loopback, null target, no self-update capability, meta
offline) and two update-all cases (mixed-fleet dispatch, all-targets-
null skip). capability-registry-pilot rewired to assert the new
filter set.