feat(alerting): AlertingProperties + AlertStateTransitions state machine

- AlertingProperties @ConfigurationProperties with effective*() accessors and
  5000 ms floor clamp on evaluatorTickIntervalMs; warn logged at startup
- AlertStateTransitions pure static state machine: Clear/Firing/Batch/Error
  branches, PENDING→FIRING promotion on forDuration elapsed; Batch delegated
  to job
- AlertInstance wither helpers: withState, withFiredAt, withResolvedAt, withAck,
  withSilenced, withTitleMessage, withLastNotifiedAt, withContext
- AlertingBeanConfig gains @EnableConfigurationProperties(AlertingProperties),
  alertingInstanceId bean (hostname:pid), alertingClock bean,
  PerKindCircuitBreaker bean wired from props
- 12 unit tests in AlertStateTransitionsTest covering all transitions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-19 19:58:12 +02:00
parent f8cd3f3ee4
commit 657dc2d407
5 changed files with 461 additions and 0 deletions

View File

@@ -34,4 +34,62 @@ public record AlertInstance(
targetGroupIds = targetGroupIds == null ? List.of() : List.copyOf(targetGroupIds);
targetRoleNames = targetRoleNames == null ? List.of() : List.copyOf(targetRoleNames);
}
// --- Wither helpers (return a new record with one field changed) ---
public AlertInstance withState(AlertState s) {
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
s, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, silenced,
currentValue, threshold, context, title, message,
targetUserIds, targetGroupIds, targetRoleNames);
}
public AlertInstance withFiredAt(Instant i) {
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
state, severity, i, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, silenced,
currentValue, threshold, context, title, message,
targetUserIds, targetGroupIds, targetRoleNames);
}
public AlertInstance withResolvedAt(Instant i) {
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
state, severity, firedAt, ackedAt, ackedBy, i, lastNotifiedAt, silenced,
currentValue, threshold, context, title, message,
targetUserIds, targetGroupIds, targetRoleNames);
}
public AlertInstance withAck(String ackedBy, Instant ackedAt) {
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, silenced,
currentValue, threshold, context, title, message,
targetUserIds, targetGroupIds, targetRoleNames);
}
public AlertInstance withSilenced(boolean silenced) {
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, silenced,
currentValue, threshold, context, title, message,
targetUserIds, targetGroupIds, targetRoleNames);
}
public AlertInstance withTitleMessage(String title, String message) {
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, silenced,
currentValue, threshold, context, title, message,
targetUserIds, targetGroupIds, targetRoleNames);
}
public AlertInstance withLastNotifiedAt(Instant instant) {
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, instant, silenced,
currentValue, threshold, context, title, message,
targetUserIds, targetGroupIds, targetRoleNames);
}
public AlertInstance withContext(Map<String, Object> context) {
return new AlertInstance(id, ruleId, ruleSnapshot, environmentId,
state, severity, firedAt, ackedAt, ackedBy, resolvedAt, lastNotifiedAt, silenced,
currentValue, threshold, context, title, message,
targetUserIds, targetGroupIds, targetRoleNames);
}
}