Skip to content

feat: decide which server events raise an alert and remember what was already reported - #342

Merged
4 commits merged into
mainfrom
feat/alerts-evaluation-and-state
Nov 13, 2026
Merged

4 commits merged into
mainfrom
feat/alerts-evaluation-and-state

Conversation

@nofuturekid

Copy link
Copy Markdown
Collaborator

What changed

First pull request of alerts outside the app (design on #321). It adds only the pure decision logic and its stored state. Nothing runs in the background yet.

  • evaluateAlerts (data/alerts/AlertRules.kt): given what one check of one server returned and the remembered state of that server, it returns the alerts to post now and the next state.
    • Each new unread notification of importance alert or warning is posted once per id, never twice (also not after it left the unread list). A later notification with the same text has a new id and is posted.
    • The array conditions "not started" and "degraded" are the rules of attentionItems, wrapped and limited to those two keys (not forked, not changed). They are posted when they begin and again only after they were gone at an in-between check.
    • The first successful read of a server only records what is already there and posts nothing. A failed or unreachable read changes nothing and posts nothing, and does not count as the first read.
    • The level filters: "Alerts only" posts critical items (alert notifications, degraded array); "Alerts and warnings" also posts warnings (warning notifications, array not started). Filtered items are still remembered.
    • At most 5 alerts per server and check get their own notification, most urgent first; the rest are counted for a summary line ("and 3 more on Tower").
    • The remembered ids are capped at 300; ids still on the server are never forgotten before ids that are gone.
  • State store (AlertStateStore, ServerAlertState): one small interface, persisted in the existing DataStore under a new key at the end of SettingsStore (JSON per server id), with get, put, remove and clear. ServerRepository.delete removes the server's state next to its key and pin.
  • One-shot seam (ServerProbe, ProbeResult): the interface for "fetch what one check needs from one server once". No implementation; only a test fake. The server-facing implementation arrives with the worker.
  • ADR-0048 (opt-in background alert checks) records the decision of 2026-11-13, the rules above and their trade-offs. ADR-0017 gets a dated amendment line (the foreground-only rule stays; the opt-in job is the one exception), and the ADR index has the new row. 0048 is used because 0047 is taken by another change in flight.

Why

Users want to know about a failing disk or an array that is not started without opening NOVA. Getting the rules and the memory right first, with tests, means the worker, the settings screen and a later widget only have to use them.

Tests

New unit tests under app/src/test/.../data/alerts/, each stating why it exists:

  • AlertRulesTest (26): first check seeds silently; one alert per notification id; level filter; array condition once per episode; unreachable server is silent and keeps its memory; each failed half keeps its state; flood cap; the id list stays bounded; STATE_ALERT_KEYS is exactly the two array keys (disk and temperature items are not posted again).
  • AlertCheckCycleTest (6): whole checks over several rounds and two servers on the in-memory fakes: state is per server, removed with the server, cleared when alerts are switched off, and bounded.
  • AlertStateStoreTest (7, Robolectric, real DataStore): state survives a new store object over the same file, servers do not share state, deleting a server entry removes its state.

./scripts/local-ci.sh passes (lint, debug and release builds of both flavors, unit tests).

Deliberately left out

No background work, no WorkManager dependency, no permission, no notification channel, no settings UI, no real server query, no CHANGELOG entry (nothing is user-visible yet), no version bump, no documentation for users. The capability model, the Docker, VMs and Array tabs, the update code, the manifest, build files and .github/ are untouched. The last-check record and the address order of the design's later changes are not here either.

Refs #321

🤖 Generated with Claude Code

nofuturekid and others added 4 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>
… already reported

First part of alerts outside the app (#321): the pure decision logic and its
stored state. Nothing runs in the background yet.

evaluateAlerts takes what one check of one server returned and the remembered
state, and returns the alerts to post and the next state. New unread alert and
warning notifications are posted once per id; the array conditions "not started"
and "degraded" (the rules of attentionItems, wrapped) are posted when they begin
and again only after they were gone. The first successful read only records, a
failed read changes nothing, the level filters, at most five alerts per server
and check get their own notification, and the remembered ids are capped.

The state is stored per server in the existing DataStore and removed with the
server entry. ServerProbe is the interface for fetching one check once; only a
test fake exists. ADR-0048 records the decision and amends ADR-0017.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- An array with state Offline (not known) now counts as not loaded, so it
  neither clears the open conditions nor sets the baseline.
- "Alerts only" no longer suppresses the two array conditions; Unraid never
  notifies about them. ADR-0048 item 5 updated.
- ADR-0048: only WAKE_LOCK and RECEIVE_BOOT_COMPLETED are new permissions;
  ACCESS_NETWORK_STATE is already declared, FOREGROUND_SERVICE is stripped.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@nofuturekid
nofuturekid force-pushed the feat/alerts-evaluation-and-state branch from b34dc02 to 816d88d 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/alerts-evaluation-and-state 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