sqlinsight-pro/src/SQLInsight.Rules/Servers/ScopedRules.cs
Søren Eggert Lundsteen Olsen ba7c751b5c Rules: Windows servers (SQLI-WIN-001..013) and synthetic checks (SQLI-SYN-001..003)
Scoped rules with their own context (a Windows server, a synthetic check) that
produce the same findings and outcomes as the instance rules; missing data is
"ikke målbar" with the collector's reason. Thresholds are settings
(SQLInsight:Servers, SQLInsight:SyntheticChecks).

- Sustained CPU, low memory (share and MB), paging with low memory, commit charge.
- Free disk space and "fuld om ca. N dage" forecasts.
- Stopped automatic services (minus those that stop by design, and a grace
  period after a restart) and watched services that must run.
- Windows event log groups: important kinds at once, ordinary errors when they
  repeat, capped per server; sample messages are sensitive.
- Unexpected restart (Kernel-Power 41, EventLog 6008, BugCheck), optional uptime.
- Scheduled tasks that failed or did not run as planned.
- Synthetic checks failing (twice in a row), "svarer ikke, selv om tjenesten
  kører", slow answers, TLS certificates about to expire.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:58:18 +02:00

90 lines
3.4 KiB
C#

using SQLInsight.Core.Collection;
using SQLInsight.Core.Findings;
using SQLInsight.Rules.Engine;
namespace SQLInsight.Rules.Servers;
/// <summary>
/// A rule about something that is not a SQL Server instance (a Windows server, a synthetic check). It produces the
/// same findings, explanations and outcomes as the instance rules, against its own kind of context.
/// </summary>
public interface IScopedRule<in TContext>
{
/// <summary>Stable identifier, e.g. <c>SQLI-WIN-001</c>. Never reused for a different check.</summary>
string Id { get; }
string Title { get; }
RuleEvaluation Evaluate(TContext context);
}
public abstract class ScopedRule<TContext> : IScopedRule<TContext>
{
public abstract string Id { get; }
public abstract string Title { get; }
public abstract RuleEvaluation Evaluate(TContext context);
protected Finding CreateFinding(Severity severity, string title, string explanation, string recommendation, IEnumerable<Evidence>? evidence = null,
string? subject = null, params Reference[] references) => new()
{
RuleId = Id,
Severity = severity,
Title = title,
Subject = subject,
Explanation = explanation,
Recommendation = recommendation,
Evidence = evidence?.ToList() ?? [],
References = references,
};
protected static RuleEvaluation Result(IEnumerable<Finding> findings)
{
var list = findings.ToArray();
return list.Length == 0 ? RuleEvaluation.Passed() : RuleEvaluation.Found(list);
}
}
/// <summary>Runs scoped rules. Like <see cref="RuleEngine"/>: a rule that cannot run is not measurable (with the reason) or an error, never silently passed.</summary>
public static class ScopedRuleEngine
{
public static IReadOnlyList<RuleResult> Evaluate<TContext>(IEnumerable<IScopedRule<TContext>> rules, TContext context)
{
ArgumentNullException.ThrowIfNull(rules);
return rules.Select(rule => Evaluate(rule, context)).ToList();
}
private static RuleResult Evaluate<TContext>(IScopedRule<TContext> rule, TContext context)
{
try
{
var evaluation = rule.Evaluate(context);
return new RuleResult { RuleId = rule.Id, RuleTitle = rule.Title, Outcome = evaluation.Outcome, Findings = evaluation.Findings, Reason = evaluation.Reason };
}
catch (DataUnavailableException ex)
{
return new RuleResult
{
RuleId = rule.Id,
RuleTitle = rule.Title,
Outcome = ex.NotApplicable ? RuleOutcome.NotApplicable : RuleOutcome.NotMeasurable,
Reason = ex.Message,
};
}
#pragma warning disable CA1031 // One faulty rule must not stop the others; the failure is reported, not hidden.
catch (Exception ex)
#pragma warning restore CA1031
{
return new RuleResult { RuleId = rule.Id, RuleTitle = rule.Title, Outcome = RuleOutcome.Error, Reason = $"{ex.GetType().Name}: {ex.Message}" };
}
}
}
/// <summary>Every rule about Windows servers and synthetic checks, for rule listings.</summary>
public static class ServerRuleCatalog
{
public static IReadOnlyList<(string Id, string Title)> All =>
WindowsServerRules.Create().Select(r => (r.Id, r.Title))
.Concat(SyntheticCheckRules.Create().Select(r => (r.Id, r.Title)))
.ToList();
}