feat(alerts): a rule can fire when a reading rises, "rose by N" (#6724)

A rule compared a reading with a fixed number, so Synology Disk 1's bad
sectors going from 3,160 to 3,392 could say nothing: a count that only grows
is soon past any line. "rose by" compares each reading with the one before it,
kept on the alert's state and only for such a rule. A fall (a replaced disk)
doesn't fire. On the rule form, through MCP, and worded in emails, the alert
sentence, history and the timeline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-30 19:03:29 -04:00
co-authored by Claude Opus 5.5
parent d83f8ffe9b
commit e51d969c43
15 changed files with 133 additions and 12 deletions
+16
View File
@@ -100,3 +100,19 @@ def test_the_verdict_counts_what_fired_and_for_how_long():
def test_an_up_or_down_rule_says_the_word():
assert fires_when(_rule(metric="is_up", op=AlertOperator.lt, threshold=0.5)) == "fires when down"
def test_a_rule_on_a_rise_reads_as_one():
rule = _rule(metric="disk_bad_sectors", op=AlertOperator.rose, threshold=1.0)
assert fires_when(rule).startswith("fires on a rise of ")
assert AlertOperator.rose.said == "rose by" and AlertOperator.gt.said == ">"
def test_a_rise_is_against_the_reading_before_it():
from steward.core.alerts import _is_breached
rose = AlertOperator.rose
assert _is_breached(3392.0, rose, 1.0, previous=3160.0)
assert not _is_breached(3392.0, rose, 1.0, previous=3392.0) # flat
assert not _is_breached(0.0, rose, 1.0, previous=3392.0) # a replaced disk starts over
assert not _is_breached(3392.0, rose, 1.0) # nothing to have risen from
assert not _is_breached(3161.0, rose, 5.0, previous=3160.0) # less than asked