Skip to content

feat: detect the API version of the server and what the API key may do - #343

Merged
2 commits merged into
mainfrom
feat/server-capabilities-foundation
Nov 13, 2026
Merged

2 commits merged into
mainfrom
feat/server-capabilities-foundation

Conversation

@nofuturekid

@nofuturekid nofuturekid commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

What changed

NOVA now knows which Unraid API version the active server runs and which roles and permissions its API key holds. This is the foundation for hiding controls a read-only key cannot use (#315), the "API too old" hint (#247), the one-step container restart (#317) and the key-permission documentation (#316). Nothing reads it yet, so there is no visible change.

  • ServerCapabilities and its small types (ApiVersion, ApiFeature, KeyAction, Access, Support, KeyAccess, Detection) in data/model/ServerCapabilities.kt, pure Kotlin. access(KeyAction) uses the static role table of the Unraid API plus the key's explicit permissions; supports(ApiFeature) compares against the floors 4.30.0, 4.35.0 and 4.36.0 (a build suffix such as +8e587fe2 is ignored).
  • Two detection operations appended to queries.graphql, GetApiVersion and GetKeyAccess, and the matching additions to the client schema.graphqls. They are separate because me is non-null: a FORBIDDEN on it would otherwise lose the version too.
  • CapabilityProbe.kt sends them and classifies the answers by extensions.code: UNAUTHENTICATED is a rejected key, a validation failure on the version means an API older than 4.15.0, FORBIDDEN leaves the access unknown, transport and other errors are a failed detection. The mapping lives in its own file; GraphQlMapper.kt is untouched.
  • CapabilityRepository (a singleton built like UpdateTracker) holds the result in memory and MainViewModel.capabilities exposes it. Detection runs only when the server, its address or the key changes, on pull-to-refresh, and when the app returns to the foreground. No timer, no retry loop, no polling.
  • ADR-0047 and its index row.

Unknown is never denied: a failed detection, a refused me and a role NOVA does not know leave the access unknown, so controls behave as today. A failed re-detection keeps the last good result for the same target, a new target starts unknown, and a late answer for an old target is dropped.

Tests

Unit tests encode why: a read-only key is denied every control, ADMIN is allowed everything, explicit permissions add to a role, *_OWN does not count, an unknown role stays unknown, every mutation in mutations.graphql belongs to exactly one KeyAction, version parsing and the floors, each server answer class (bodies taken from a real Unraid 7.4.0 server), and in the repository: no re-probe for a rename, joining a running detection, no probe without a trigger after an hour of virtual time, no retry after a failure.

Smoke check (read-only, VIEWER key, real server on Unraid 7.4.0): both operations answer HTTP 200 with the shapes the tests use (info.versions.core.{unraid,api} and me.{roles,permissions}), no errors.

Deliberately left out

Hiding controls and the "View only" note, per-tab error reasons, the restart mutation, the Add server Test, documentation of key permissions, version bump and CHANGELOG (nothing user-visible changes). DockerTab, VmsTab, ArrayTab, the Overview, the update code, GraphQlMapper.kt, SettingsStore.kt and .github/ are not touched.

Two small deviations from the written design (see ADR-0047)

  • The detection pipeline lives in the application scope instead of being shared only while a screen collects, so a refresh cannot be lost when the screen re-subscribes at the same moment.
  • The foreground refresh fires on every start of the main screen (setAppVisible(true)); it joins a detection that is already running, so the first start does not probe twice.

Refs #315
Refs #247
Refs #317
Refs #316

🤖 Generated with Claude Code

nofuturekid and others added 2 commits November 13, 2026 18:31
Two small queries (GetApiVersion, GetKeyAccess) tell NOVA which Unraid
API version the active server runs and which roles and permissions its
API key holds. The result is kept in memory as ServerCapabilities and
exposed as MainViewModel.capabilities; nothing reads it yet, so there is
no visible change.

Detection runs when the server, its address or the key changes, on
pull-to-refresh, and when the app returns to the foreground. There is no
timer, no retry loop and no polling.

Unknown is never denied: a failed detection, a refused `me` and a role
NOVA does not know leave the access unknown, so controls stay as they are
today. Only a successful answer can say "denied".

The model, the role table, the classification of server answers and the
repository wiring have unit tests built on the real response bodies of an
Unraid 7.4.0 server. ADR-0047 records the decision.

Refs #315, #247, #317, #316

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Refresh the capabilities on every ON_START so a fresh MainViewModel after a
finished activity re-detects (the repository joins a running detection, so the
first start does not probe twice). Correct the schema comment about unknown
enum values (UNKNOWN__, no raw name) and the CapabilityProbeTest KDoc about
which bodies are recorded and which are constructed. ADR-0047 follows.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@nofuturekid
nofuturekid force-pushed the feat/server-capabilities-foundation branch from 4a368b8 to 6aec2a0 Compare November 13, 2026 17:36
@nofuturekid nofuturekid closed this pull request by merging all changes into main in 00c999b Nov 13, 2026
@nofuturekid
nofuturekid deleted the feat/server-capabilities-foundation branch November 13, 2026 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant