package checker import ( "context" "fmt" sdk "git.happydns.org/checker-sdk-go/checker" ) // Rules returns the rule set surfaced to happyDomain. After the // registry refactor we expose a single, generic rule that emits one // CheckState per source result: the per-source verdict lives in // CheckState.Subject (the source name) and CheckState.Code carries the // canonical hit / clean / disabled / error flavour. func Rules() []sdk.CheckRule { return []sdk.CheckRule{&sourceRule{}} } type sourceRule struct{} func (*sourceRule) Name() string { return "source_listed" } func (*sourceRule) Description() string { return "Emits one state per reputation source: Critical/Warning when the source flags the domain, OK when clean, Info when the source is disabled, and Warning on transient query errors." } func (*sourceRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState { var data BlacklistData if err := obs.Get(ctx, ObservationKeyBlacklist, &data); err != nil { return []sdk.CheckState{{ Status: sdk.StatusError, Message: fmt.Sprintf("failed to get observation: %v", err), Code: "blacklist_obs_error", }} } if len(data.Results) == 0 { return []sdk.CheckState{{ Status: sdk.StatusInfo, Message: "No reputation sources registered.", Code: "blacklist_no_sources", }} } out := make([]sdk.CheckState, 0, len(data.Results)) for _, r := range data.Results { out = append(out, evaluateOne(r)) } return out } func evaluateOne(r SourceResult) sdk.CheckState { subj := r.SourceName if r.Subject != "" && r.Subject != r.SourceName { subj = r.SourceName + " / " + r.Subject } switch { case !r.Enabled: return sdk.CheckState{ Status: sdk.StatusUnknown, Subject: subj, Message: subj + ": disabled or not configured.", Code: "source_disabled", } case r.BlockedQuery: return sdk.CheckState{ Status: sdk.StatusError, Subject: subj, Message: fmt.Sprintf("%s: resolver is blocked, result unreliable: %s", subj, joinNonEmpty(r.Reasons, "; ")), Code: "source_resolver_blocked", } case r.Error != "": return sdk.CheckState{ Status: sdk.StatusWarn, Subject: subj, Message: subj + ": query failed: " + r.Error, Code: "source_error", } case r.Listed: return sdk.CheckState{ Status: severityToStatus(r.Severity), Subject: subj, Message: fmt.Sprintf("Listed in %s: %s", subj, joinNonEmpty(r.Reasons, "; ")), Code: "source_listed", Meta: map[string]any{ "source_id": r.SourceID, "reasons": r.Reasons, "lookup_url": r.LookupURL, "removal_url": r.RemovalURL, "reference": r.Reference, }, } default: return sdk.CheckState{ Status: sdk.StatusOK, Subject: subj, Message: subj + ": clean.", Code: "source_clean", } } } func severityToStatus(sev string) sdk.Status { switch sev { case SeverityCrit: return sdk.StatusCrit case SeverityWarn: return sdk.StatusWarn case SeverityInfo: return sdk.StatusInfo case SeverityOK: return sdk.StatusOK } return sdk.StatusCrit }