fix: 12h cooldown before reopening manually closed host_silence (0.9.9)

Track resolved_by on problems; suppress scan/ingest recreate after manual
resolve; reopen same problem after cooldown; auto-close via heartbeat unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-11 10:33:55 +10:00
parent befaf86bf4
commit 7df718eed3
11 changed files with 285 additions and 7 deletions
+79
View File
@@ -95,6 +95,85 @@ def test_scan_skips_host_without_heartbeat(db_session, scan_settings):
assert results == []
def test_scan_suppresses_after_manual_resolve_within_cooldown(db_session, scan_settings, monkeypatch):
monkeypatch.setenv("SAC_HOST_SILENCE_MANUAL_RESOLVE_COOLDOWN_HOURS", "12")
get_settings.cache_clear()
hb = _ingest(
db_session,
type="agent.heartbeat",
category="agent",
severity="info",
title="hb",
summary="heartbeat",
)
hb.received_at = datetime.now(timezone.utc) - timedelta(hours=2)
db_session.flush()
now = datetime.now(timezone.utc)
first = run_host_silence_scan(db_session, now=now)
assert first[0].created is True
problem = first[0].problem
problem.status = "resolved"
problem.resolved_by = "manual"
problem.updated_at = now
db_session.flush()
second = run_host_silence_scan(db_session, now=now + timedelta(minutes=10))
assert second == []
open_count = db_session.scalar(
select(Problem).where(
Problem.host_id == hb.host_id,
Problem.rule_id == RULE_HOST_SILENCE,
Problem.status == "open",
)
)
assert open_count is None
resolved_count = len(
db_session.scalars(
select(Problem).where(
Problem.host_id == hb.host_id,
Problem.rule_id == RULE_HOST_SILENCE,
Problem.status == "resolved",
)
).all()
)
assert resolved_count == 1
def test_scan_reopens_after_manual_cooldown_expires(db_session, scan_settings, monkeypatch):
monkeypatch.setenv("SAC_HOST_SILENCE_MANUAL_RESOLVE_COOLDOWN_HOURS", "12")
get_settings.cache_clear()
hb = _ingest(
db_session,
type="agent.heartbeat",
category="agent",
severity="info",
title="hb",
summary="heartbeat",
)
hb.received_at = datetime.now(timezone.utc) - timedelta(hours=2)
db_session.flush()
now = datetime.now(timezone.utc)
first = run_host_silence_scan(db_session, now=now)
problem = first[0].problem
problem.status = "resolved"
problem.resolved_by = "manual"
problem.updated_at = now - timedelta(hours=13)
db_session.flush()
later = run_host_silence_scan(db_session, now=now)
assert len(later) == 1
assert later[0].created is True
assert later[0].problem.id == problem.id
assert later[0].problem.status == "open"
assert later[0].problem.resolved_by is None
def test_scan_notifies_only_on_create(db_session, scan_settings):
hb = _ingest(
db_session,
+96
View File
@@ -182,3 +182,99 @@ def test_heartbeat_resolves_host_silence(db_session, rule_settings):
maybe_create_problem(db_session, fresh_hb)
db_session.refresh(open_silence)
assert open_silence.status == "resolved"
assert open_silence.resolved_by == "auto"
def test_host_silence_suppressed_after_manual_resolve_on_ingest(db_session, rule_settings, monkeypatch):
monkeypatch.setenv("SAC_HOST_SILENCE_MANUAL_RESOLVE_COOLDOWN_HOURS", "12")
get_settings.cache_clear()
hb = _ingest(
db_session,
type="agent.heartbeat",
category="agent",
severity="info",
title="hb",
summary="heartbeat",
)
hb.received_at = datetime.now(timezone.utc) - timedelta(hours=2)
db_session.flush()
trigger = _ingest(
db_session,
type="ssh.login.success",
severity="info",
title="ok",
summary="login",
)
problem, created = maybe_create_problem(db_session, trigger)
assert created is True
problem.status = "resolved"
problem.resolved_by = "manual"
problem.updated_at = datetime.now(timezone.utc)
db_session.flush()
trigger2 = _ingest(
db_session,
type="ssh.login.success",
severity="info",
title="ok2",
summary="login2",
)
problem2, created2 = maybe_create_problem(db_session, trigger2)
assert problem2 is None
assert created2 is False
open_silence = db_session.scalar(
select(Problem).where(
Problem.rule_id == RULE_HOST_SILENCE,
Problem.status == "open",
)
)
assert open_silence is None
def test_host_silence_reopens_after_auto_resolve_when_still_stale(db_session, rule_settings, monkeypatch):
monkeypatch.setenv("SAC_HOST_SILENCE_MANUAL_RESOLVE_COOLDOWN_HOURS", "12")
get_settings.cache_clear()
hb = _ingest(
db_session,
type="agent.heartbeat",
category="agent",
severity="info",
title="hb",
summary="heartbeat",
)
hb.received_at = datetime.now(timezone.utc) - timedelta(hours=2)
db_session.flush()
trigger = _ingest(
db_session,
type="ssh.login.success",
severity="info",
title="ok",
summary="login",
)
problem, created = maybe_create_problem(db_session, trigger)
assert created is True
from app.services.problem_rules import resolve_host_silence_problems
resolve_host_silence_problems(db_session, hb.host_id)
db_session.refresh(problem)
assert problem.status == "resolved"
assert problem.resolved_by == "auto"
trigger2 = _ingest(
db_session,
type="ssh.login.success",
severity="info",
title="ok2",
summary="login2",
)
problem2, created2 = maybe_create_problem(db_session, trigger2)
assert created2 is True
assert problem2 is not None
assert problem2.status == "open"
assert problem2.id != problem.id