report

package
v0.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package report builds the fleet comparison report from per-cluster scanner results, including divergences, findings, scores, outliers, and trends.

Index

Constants

View Source
const (
	NormKindNumeric = "numeric"
	NormKindString  = "string"
	NormKindSet     = "set"
)

Norm kinds name the detector that measured a fleet-relative field.

View Source
const (
	// PersistenceChronic marks a finding present in most scans in the window.
	PersistenceChronic = "chronic"
	// PersistenceIntermittent marks a finding that comes and goes.
	PersistenceIntermittent = "intermittent"
	// PersistenceTransient marks a finding seen in only a few scans.
	PersistenceTransient = "transient"
)

Persistence classes describe how consistently a finding recurs across a window of scans. The data carries no operator resolution label, so recurrence across scans is the strongest learned signal available for whether a finding is a standing problem worth attention or a transient blip.

View Source
const (
	SeverityInfo     = "info"
	SeverityWarning  = "warning"
	SeverityCritical = "critical"
)

Severity levels for divergences.

View Source
const (
	TermCriticalFindings = "critical-findings"
	TermWarningFindings  = "warning-findings"
	TermInfoFindings     = "info-findings"
	TermCriticalClusters = "critical-clusters"
	TermDegradedClusters = "degraded-clusters"
	TermVersionSkew      = "version-skew"
	TermWorstNode        = "worst-node"
)

Score terms name the cost categories ComputeFleetScore charges.

View Source
const (
	// FixMedian sets a numeric field to the scope median.
	FixMedian = "median"
	// FixMode sets a string field to the scope mode.
	FixMode = "mode"
	// FixConsensus adds the consensus members a set field lacks.
	FixConsensus = "consensus"
	// FixRemove takes the cluster out of the population entirely.
	FixRemove = "remove"
)

Fix kinds name what a what-if does to one cluster's field.

View Source
const NormScopeFleet = "fleet"

NormScopeFleet is the scope of a norm computed over the whole fleet. A cohort norm carries the cohort name instead.

Variables

View Source
var ChartJS []byte

ChartJS is the vendored Chart.js build. The standalone report inlines it so the file renders offline from file://, and the server serves the same bytes at /vendor/chart.min.js.

View Source
var ClusterColors = []string{
	"#58a6ff", "#f85149", "#3fb950", "#d29922",
	"#bc8cff", "#39d353", "#f78166", "#8b949e",
}

ClusterColors assigns a consistent color to each cluster for visual tracking.

View Source
var ErrBuild = errors.New("build report")

ErrBuild indicates a failure building the comparison report.

View Source
var ErrWhatIf = errors.New("what-if")

ErrWhatIf indicates a what-if fix that does not apply to the scan.

View Source
var ScannerDescriptions = map[string]string{
	"version":           "API server version, with semver-aware skew severity.",
	"namespaces":        "Namespace lists, labels, and Pod Security Standards labels.",
	"crds":              "Installed CustomResourceDefinitions, compared as a set of names.",
	"services":          "Services across all namespaces, their types and ports.",
	"ingresses":         "Ingress resources, their classes, TLS, and hosts.",
	"resources":         "Node count, allocatable CPU and memory, and scheduling status.",
	"rbac":              "ClusterRoles, Roles, and their bindings.",
	"security":          "Pod Security Standards enforcement labels on every namespace.",
	"network-policies":  "NetworkPolicy coverage per namespace.",
	"resource-quotas":   "ResourceQuota and LimitRange objects.",
	"node-health":       "Node conditions: Ready, MemoryPressure, DiskPressure, PIDPressure.",
	"metrics":           "CPU and memory utilization from metrics-server.",
	"events":            "Warning events in the last hour, by reason.",
	"workload-security": "Privileged containers, host namespaces, added capabilities, and root users.",
	"rbac-audit":        "Cluster-admin bindings, wildcard rules, and default service account grants.",
	"image-audit":       "Images on :latest or no tag, without digest pins, or from deprecated registries.",
	"certs":             "Expiry of TLS Secrets, Ingress TLS, and webhook CA bundles.",
	"deprecated-apis":   "API versions in use that upcoming Kubernetes releases deprecate or remove.",
	"workload-coverage": "PodDisruptionBudget and HorizontalPodAutoscaler coverage of replicated workloads.",
	"cluster-info":      "Node OS, kernel, runtime, and kubelet version drift within a cluster.",
	"admission":         "Webhook configurations with no healthy endpoints or expiring CA bundles.",
	"geo":               "Cluster location from node topology region and zone labels.",
	"vulnerabilities":   "Image vulnerability counts from Trivy Operator VulnerabilityReports.",
	"policy-reports":    "Fail and warn results from Kyverno, Gatekeeper, and other PolicyReport writers.",
}

ScannerDescriptions maps scanner keys to one line on what each checks.

View Source
var ScannerLabels = map[string]string{
	"version":           "Kubernetes Version",
	"namespaces":        "Namespaces",
	"crds":              "Custom Resource Definitions",
	"services":          "Services",
	"ingresses":         "Ingress Resources",
	"resources":         "Node Resources",
	"rbac":              "RBAC Configuration",
	"security":          "Pod Security Standards",
	"network-policies":  "Network Policies",
	"resource-quotas":   "Resource Quotas and Limits",
	"node-health":       "Node Health",
	"metrics":           "Resource Utilization",
	"events":            "Cluster Events",
	"workload-security": "Workload Security",
	"rbac-audit":        "RBAC Audit",
	"image-audit":       "Image Hygiene",
	"certs":             "Certificate Expiry",
	"deprecated-apis":   "Deprecated APIs",
	"workload-coverage": "PDB/HPA Coverage",
	"cluster-info":      "Node OS/Kernel Drift",
	"admission":         "Admission Webhooks",
	"geo":               "Geographic Location",
	"vulnerabilities":   "Image Vulnerabilities",
	"policy-reports":    "Policy Reports",
}

ScannerLabels maps scanner keys to human-readable names.

View Source
var SystemNamespaceExact = map[string]bool{
	"default":         true,
	"kube-system":     true,
	"kube-public":     true,
	"kube-node-lease": true,
}

SystemNamespaceExact are the namespace names treated as system regardless of prefix matching.

View Source
var SystemNamespacePrefixes = []string{
	"kube-",
	"gke-",
	"eks-",
	"aks-",
	"cattle-",
	"istio-",
	"linkerd-",
	"cert-manager",
	"monitoring",
	"logging",
	"velero",
	"prometheus",
}

SystemNamespacePrefixes are the namespace name prefixes excluded from fleet-divergence findings. Cloud and add-on namespaces legitimately differ across providers; flagging them as drift drowns real signal.

Functions

func ClusterCharges added in v0.12.0

func ClusterCharges(cluster string, findings []Finding) (crit, warn int)

ClusterCharges counts the critical and warning findings a cluster pays for: its own findings at their severity, and each mark a fleet-scoped finding puts on it at the mark's severity. A fleet finding with no mark on the cluster costs it nothing, which is the same rule GenerateClusterHealth uses for status, so the two never disagree about a finding.

func CohortMapFor added in v0.10.0

func CohortMapFor(r *Report) cohort.Map

CohortMapFor lays the fleet of r out on a cohort map, from the same feature vectors cohort assignment uses, with r's cohorts as the groups and five nearest peers per cluster. It is computed on demand rather than in Build because classical scaling is cubic in the fleet size.

func FindingFingerprint added in v0.10.0

func FindingFingerprint(f Finding) string

FindingFingerprint returns the stable identifier of a finding: a hash of its cluster, scanner, and rule. It never reads the title, so counts in the title can change without orphaning acks or breaking persistence.

func GroupFindingsByCluster added in v0.10.0

func GroupFindingsByCluster(findings []Finding) map[string][]Finding

GroupFindingsByCluster buckets findings by cluster, using "fleet" as the key for cross-cluster findings.

func IsSystemNamespace

func IsSystemNamespace(name string) bool

IsSystemNamespace reports whether a namespace name should be treated as a system namespace and therefore excluded from divergence findings.

func RenderHTML

func RenderHTML(r *Report) ([]byte, error)

RenderHTML produces a self-contained HTML dashboard from a Report. The report data is embedded as a JSON blob and all rendering happens client-side in JS.

func Rescore added in v0.10.0

func Rescore(r *Report)

Rescore sets r's fleet score and its attribution from r's current findings and cluster health, so a caller that changes findings after Build keeps the score and its ledger in agreement. Safe to call on nil.

func RuleTitle added in v0.10.0

func RuleTitle(rule string) string

RuleTitle returns a label for a rule that names no cluster and no count. Field-derived rules are described from their field. An unknown rule is returned unchanged.

func VersionSkewSeverity

func VersionSkewSeverity(versions []string) string

VersionSkewSeverity returns the severity for a set of Kubernetes version strings present across a fleet. Patch differences are info, single-minor skew is warning, and skew of more than one minor (outside Kubernetes' own supported skew policy) is critical. Strings that fail to parse are ignored, so a single garbled response cannot escalate severity.

func WhatIfRefusal added in v0.11.0

func WhatIfRefusal(scanner, field string) string

WhatIfRefusal returns why no what-if can project a fix to scanner/field, or "" when one can. A set field derived from objects that a member alone cannot rebuild, such as the ingress classes read off ingresses, has no fix that does not invent those objects.

Types

type Brief added in v0.4.0

type Brief struct {
	// Headline is the one-line status.
	Headline string `json:"headline"`
	// Lines are the supporting summary points, most important first.
	Lines []string `json:"lines,omitempty"`
}

Brief is a short, deterministic executive summary of a fleet report, suitable for the top of a dashboard or a status email. It is templated from the report with no randomness, so the same scan always yields the same words.

func GenerateBrief added in v0.4.0

func GenerateBrief(r *Report) Brief

GenerateBrief synthesizes an executive summary from a fully built report: the score, the worst incident, cohort drift, degraded coverage, and the fixes that matter most.

type BuildOptions

type BuildOptions struct {
	// OutlierThreshold controls outlier sensitivity (standard deviations).
	// Lower values flag more outliers. Default is 3.5.
	OutlierThreshold float64
	// Groups maps group name to cluster names for group-aware analysis.
	Groups map[string][]string
	// ClusterTags maps cluster name to a cohort tag value. When non-empty,
	// tagged clusters land in tagged cohorts instead of being auto-grouped.
	// Untagged clusters still get auto-cohorted.
	ClusterTags map[string]string
	// Timestamp is when the scan behind the report ran. A report rebuilt from a stored scan
	// passes that scan's time, so rebuilding it later yields the same report. Zero means now.
	Timestamp time.Time
}

BuildOptions controls report generation behavior.

type CapacityAnalysis

type CapacityAnalysis struct {
	// Cluster is the cluster name.
	Cluster string `json:"cluster"`
	// CPUUtilization is the average CPU percentage.
	CPUUtilization float64 `json:"cpu_utilization"`
	// MemoryUtilization is the average memory percentage.
	MemoryUtilization float64 `json:"memory_utilization"`
	// HasMemoryPressure is true when any node reports MemoryPressure=True.
	HasMemoryPressure bool `json:"has_memory_pressure"`
	// HasDiskPressure is true when any node reports DiskPressure=True.
	HasDiskPressure bool `json:"has_disk_pressure"`
	// HasOOMEvents is true when OOMKilling events were detected.
	HasOOMEvents bool `json:"has_oom_events"`
	// HasSchedulingFailures is true when FailedScheduling events were detected.
	HasSchedulingFailures bool `json:"has_scheduling_failures"`
	// NodeCount is the total number of nodes.
	NodeCount int `json:"node_count"`
	// HealthyNodes is the number of healthy nodes.
	HealthyNodes int `json:"healthy_nodes"`
	// HeadroomCPU is the percentage of CPU capacity still available.
	HeadroomCPU float64 `json:"headroom_cpu"`
	// HeadroomMemory is the percentage of memory capacity still available.
	HeadroomMemory float64 `json:"headroom_memory"`
	// Status is the assessed state: healthy, busy, strained, critical.
	Status string `json:"status"`
	// Recommendation is a plain English capacity recommendation.
	Recommendation string `json:"recommendation,omitempty"`
	// NodesNeededForTarget is how many additional nodes would bring memory below 70%.
	NodesNeededForTarget int `json:"nodes_needed_for_target,omitempty"`
	// GroupDeviation describes how this cluster compares to its group peers.
	GroupDeviation string `json:"group_deviation,omitempty"`
}

CapacityAnalysis holds smart analysis for a single cluster, correlating multiple signals rather than applying naive thresholds.

func AnalyzeCapacity

func AnalyzeCapacity(r *Report, groups map[string][]string) []CapacityAnalysis

AnalyzeCapacity runs smart capacity analysis across all clusters in a report. Groups can be nil when no grouping is available.

type Category

type Category struct {
	// Name is the category display name.
	Name string `json:"name"`
	// Scanners lists the scanner names in this category.
	Scanners []string `json:"scanners"`
}

Category groups related scanners for report organization.

func Categories

func Categories() []Category

Categories returns the scanner groupings used in reports.

type CategoryReport

type CategoryReport struct {
	// Name is the category display name.
	Name string `json:"name"`
	// Scanners lists scanner names in this category.
	Scanners []string `json:"scanners"`
}

CategoryReport holds a category and its scanner names for the report.

type ClusterDrift added in v0.10.0

type ClusterDrift struct {
	// Cluster is the cluster name.
	Cluster string `json:"cluster"`
	// Mass is the cluster's deviation mass in each scan, oldest first, zero
	// where the cluster was absent.
	Mass []float64 `json:"mass"`
	// MassBefore is the deviation mass in the first scan.
	MassBefore float64 `json:"mass_before"`
	// MassAfter is the deviation mass in the last scan.
	MassAfter float64 `json:"mass_after"`
	// MassDelta is MassAfter - MassBefore.
	MassDelta float64 `json:"mass_delta"`
	// Fields lists up to five fields whose value changed between the first
	// and last scan, the largest move in |z| first.
	Fields []FieldDrift `json:"fields,omitempty"`
	// Gained lists outlier flags set in the last scan and not in the first.
	Gained []FlagChange `json:"gained,omitempty"`
	// Lost lists outlier flags set in the first scan and not in the last.
	Lost []FlagChange `json:"lost,omitempty"`
}

ClusterDrift is one cluster's row in a Ledger.

type ClusterHealth

type ClusterHealth struct {
	// Name is the cluster context name.
	Name string `json:"name"`
	// Status is "healthy", "busy", "degraded", or "critical".
	Status string `json:"status"`
	// FindingCounts maps severity to count for this cluster.
	FindingCounts map[string]int `json:"finding_counts"`
	// KubernetesVersion is the cluster's k8s version.
	KubernetesVersion string `json:"kubernetes_version"`
	// NodeCount is the number of nodes.
	NodeCount int `json:"node_count"`
	// HealthyNodes is the number of healthy nodes.
	HealthyNodes int `json:"healthy_nodes"`
	// AvgCPU is the average CPU utilization percentage.
	AvgCPU float64 `json:"avg_cpu"`
	// AvgMemory is the average memory utilization percentage.
	AvgMemory float64 `json:"avg_memory"`
	// WarningEvents is the number of warning events.
	WarningEvents int `json:"warning_events"`
	// NamespaceCount is the number of namespaces.
	NamespaceCount int `json:"namespace_count"`
	// DegradedScanners is how many scanner runs did not return complete,
	// trustworthy data for this cluster (errored, degraded, or unavailable).
	// Nonzero means this cluster's report rests on partial coverage.
	DegradedScanners int `json:"degraded_scanners,omitempty"`
}

ClusterHealth summarizes a single cluster's overall state.

func GenerateClusterHealth

func GenerateClusterHealth(r *Report, findings []Finding) []ClusterHealth

GenerateClusterHealth builds per-cluster health summaries. A cluster's status and counts come from its own findings and from fleet-scoped findings that mark it: a fleet finding such as version skew is the fleet's, scored once through its own term, and sets status only on the clusters it names.

type ClusterMark added in v0.11.0

type ClusterMark struct {
	// Cluster is the implicated cluster.
	Cluster string `json:"cluster"`
	// Severity is the severity the finding sets on this cluster's status.
	Severity string `json:"severity"`
}

ClusterMark ties a fleet-scoped finding to one cluster it implicates.

type ClusterScore

type ClusterScore struct {
	// Cluster is the kubeconfig context name the score applies to.
	Cluster string `json:"cluster"`
	// Score is the 0-100 health score.
	Score int `json:"score"`
	// Grade is the letter grade rollup.
	Grade string `json:"grade"`
	// Headline is a one-line plain-English summary.
	Headline string `json:"headline"`
}

ClusterScore is the per-cluster analog of FleetScore. Same 0-100 scale, computed from one cluster's findings and health rather than the whole fleet. Used for per-cluster forecasts and as a ranking key on the dashboard.

func ComputeClusterScore

func ComputeClusterScore(h ClusterHealth, findings []Finding) ClusterScore

ComputeClusterScore evaluates one cluster in isolation. It does not consult fleet-wide context (use ComputeFleetScore for that); a cluster's own findings, status, and node/utilization metrics fully determine the score.

func ComputeClusterScores

func ComputeClusterScores(r *Report) []ClusterScore

ComputeClusterScores returns one ClusterScore per cluster in the report.

type ClusterTrend

type ClusterTrend struct {
	// Cluster is the cluster name.
	Cluster string `json:"cluster"`
	// Scanner is the scanner that produces the metric.
	Scanner string `json:"scanner"`
	// Field is the metric field name.
	Field string `json:"field"`
	// Points are the historical data points, oldest first.
	Points []TrendPoint `json:"points"`
	// Direction indicates the overall trend.
	Direction TrendDirection `json:"direction"`
	// Confidence describes how trustworthy this direction is: "high", "low",
	// or empty when no direction was computed.
	Confidence string `json:"confidence,omitempty"`
	// RSquared is the coefficient of determination for the fitted slope.
	RSquared float64 `json:"r_squared,omitempty"`
}

ClusterTrend tracks how a specific metric changes over time for one cluster.

func ComputeClusterTrends

func ComputeClusterTrends(cluster string, scans []ScanMeta, resultsByScan map[string]map[string]map[string]any) []ClusterTrend

ComputeClusterTrends builds trends for a single cluster from historical scan results. resultsByScan is keyed by scanID then scanner name then field name.

type ClusterValue added in v0.10.0

type ClusterValue struct {
	// Cluster is the cluster name.
	Cluster string `json:"cluster"`
	// Number is the value of a numeric field.
	Number *float64 `json:"number,omitempty"`
	// Text is the value of a string field.
	Text string `json:"text,omitempty"`
	// Size is how many members a set field has on this cluster.
	Size int `json:"size,omitempty"`
	// Missing lists the consensus members a set field lacks on this cluster,
	// sorted. Every other consensus member is present.
	Missing []string `json:"missing,omitempty"`
	// Z is the signed modified z-score of a numeric value, zero when MAD is
	// zero. In a limited sample a flagged cluster's Z is measured against the
	// other clusters, as its finding quotes.
	Z float64 `json:"z,omitempty"`
	// Outlier is true when the detector flagged this cluster.
	Outlier bool `json:"outlier,omitempty"`
	// Explained names the operator-tagged cohort whose own norm holds this
	// fleet outlier, empty otherwise. An explained outlier raises no finding.
	Explained string `json:"explained,omitempty"`
}

ClusterValue is one cluster's value in a Distribution. Which value fields apply depends on the distribution's Kind.

type CohortSummary

type CohortSummary struct {
	// Name is the cohort label (tag value for tagged cohorts, "auto-N" for
	// auto-detected cohorts, or "fleet" for too-small fleets).
	Name string `json:"name"`
	// Source identifies how the cohort was produced.
	Source string `json:"source"`
	// Clusters lists the member cluster names, sorted.
	Clusters []string `json:"clusters"`
	// Outliers are clusters that deviate from this cohort's baseline.
	Outliers []OutlierResult `json:"outliers,omitempty"`
}

CohortSummary describes one cohort in the report: its name, how it was derived, the clusters it contains, and the outliers that show up against the cohort's own baseline rather than the fleet's. The cohort view picks up drift the fleet-level view drowns out, because edge and prod clusters have different "normal" without being wrong.

type Distribution added in v0.10.0

type Distribution struct {
	// Scope is "fleet" or the name of the cohort the population was drawn from.
	Scope string `json:"scope"`
	// Scanner is the scanner that reported the field.
	Scanner string `json:"scanner"`
	// Field is the field within the scanner's data.
	Field string `json:"field"`
	// Kind is "numeric", "string", or "set".
	Kind string `json:"kind"`
	// Values holds one entry per cluster in the population, in report cluster
	// order.
	Values []ClusterValue `json:"values"`
	// Median is the population median of a numeric field.
	Median float64 `json:"median"`
	// MAD is the median absolute deviation of a numeric field from Median.
	MAD float64 `json:"mad"`
	// Band is how far from Median a numeric value may sit before it is
	// flagged: Threshold * MAD / 0.6745. Zero when MAD is zero.
	Band float64 `json:"band"`
	// Threshold is the cutoff the detector applied: the modified z-score for
	// numeric, the minimum mode share for string, and the minimum presence
	// share for set membership.
	Threshold float64 `json:"threshold"`
	// Mode is the most common value of a string field.
	Mode string `json:"mode,omitempty"`
	// ModeShare is the fraction of the population holding Mode.
	ModeShare float64 `json:"mode_share,omitempty"`
	// Consensus lists the members of a set field present in at least
	// Threshold of the population, sorted.
	Consensus []string `json:"consensus,omitempty"`
	// SampleSize is the number of clusters in the population.
	SampleSize int `json:"sample_size"`
	// LimitedSample marks a population too small for MAD, judged by the
	// leave-one-out majority diff.
	LimitedSample bool `json:"limited_sample,omitempty"`
	// Mass is the sum over Values of |Z| capped at 10.
	Mass float64 `json:"mass"`
}

Distribution is the population a fleet-relative detector measured one field against: every cluster's value, the norm, and which clusters it flagged. It is the data behind the strip and matrix views, and its norm is the one each finding's NormBasis quotes.

type Divergence

type Divergence struct {
	// Field identifies what differs (e.g. "git_version").
	Field string `json:"field"`
	// Severity indicates how important this divergence is.
	Severity string `json:"severity"`
	// Values maps cluster name to the value it reported for this field.
	Values map[string]string `json:"values"`
}

Divergence describes a single point of difference between clusters.

type FieldDrift added in v0.10.0

type FieldDrift struct {
	// Scanner is the scanner that reported the field.
	Scanner string `json:"scanner"`
	// Field is the field name.
	Field string `json:"field"`
	// ZBefore is the cluster's z in the first scan, zero when absent.
	ZBefore float64 `json:"z_before"`
	// ZAfter is the cluster's z in the last scan, zero when absent.
	ZAfter float64 `json:"z_after"`
	// Before is the cluster's value in the first scan, empty when absent.
	Before string `json:"before,omitempty"`
	// After is the cluster's value in the last scan, empty when absent.
	After string `json:"after,omitempty"`
}

FieldDrift is one field's movement for one cluster.

type Finding

type Finding struct {
	// Title is a short description of the finding.
	Title string `json:"title"`
	// Description is a detailed explanation.
	Description string `json:"description"`
	// Severity is critical, warning, or info.
	Severity string `json:"severity"`
	// Cluster is the cluster this finding applies to, or "fleet" for cross-cluster.
	Cluster string `json:"cluster"`
	// Scanner is the scanner that produced this finding.
	Scanner string `json:"scanner"`
	// Rule names the check that produced this finding as scanner/slug, e.g.
	// "workload-security/privileged". It never carries a cluster name or a
	// count, so the same check on two clusters shares a rule.
	Rule string `json:"rule"`
	// Fingerprint is the stable identifier of this finding, a hash of its
	// cluster, scanner, and rule. Acknowledgements and persistence key on it.
	Fingerprint string `json:"fingerprint"`
	// Acked reports whether an active acknowledgement covers this finding.
	// Only the server, which holds the acknowledgements, sets it.
	Acked bool `json:"acked"`
	// Affected lists the offending resources (nodes, pods, bindings, images)
	// scoped to this finding. Empty when the finding has no specific noun.
	Affected []string `json:"affected,omitempty"`
	// Remediation is the suggested fix. Empty when no automated suggestion fits.
	Remediation *Remediation `json:"remediation,omitempty"`
	// Norm is the fleet or cohort norm a fleet-relative finding was measured
	// against. It is nil on absolute-rule findings, which is how a reader
	// tells the two classes apart.
	Norm *NormBasis `json:"norm,omitempty"`
	// Marks names the clusters a fleet-scoped finding holds responsible, each
	// with the severity it sets on that cluster's status. Empty on cluster
	// findings and on fleet findings no single cluster is behind.
	Marks []ClusterMark `json:"marks,omitempty"`
}

Finding is a human-readable issue discovered across the fleet. Findings name the affected resources (pods, nodes, bindings) inline so operators do not have to drill into the per-cluster JSON to identify what to fix.

func GenerateFindings

func GenerateFindings(r *Report) []Finding

GenerateFindings analyzes a report and produces human-readable findings. Above pairwiseFleetLimit clusters, outlier findings replace the fleet-wide version, namespace, and RBAC drift findings when any outlier exists.

func GenerateTrendFindings

func GenerateTrendFindings(clusterTrends []ClusterTrend, fleetTrends []FleetTrend) []Finding

GenerateTrendFindings produces findings from historical trends.

func SelfDriftFindings added in v0.3.0

func SelfDriftFindings(drifts []SelfDrift) []Finding

SelfDriftFindings turns self-drift detections into findings so a cluster that broke from its own history reaches the same surfaces as every other finding.

func StampFingerprints added in v0.10.0

func StampFingerprints(fs []Finding) []Finding

StampFingerprints sets each finding's Fingerprint from its cluster, scanner, and rule, and returns the same slice.

type FindingPersistence added in v0.8.0

type FindingPersistence struct {
	// Fingerprint is the stable finding identifier, matching its ack key.
	Fingerprint string `json:"fingerprint"`
	// Cluster is the finding's cluster scope, or "fleet".
	Cluster string `json:"cluster"`
	// Scanner is the scanner that produced the finding.
	Scanner string `json:"scanner"`
	// Rule is the check that produced the finding.
	Rule string `json:"rule"`
	// Title is the finding title as last observed.
	Title string `json:"title"`
	// Severity is the finding's severity as last observed.
	Severity string `json:"severity"`
	// Present is the number of scans in the window that contained the finding.
	Present int `json:"present"`
	// Total is the number of scans in the window.
	Total int `json:"total"`
	// Fraction is Present divided by Total, in the range zero to one.
	Fraction float64 `json:"fraction"`
	// Streak is the number of consecutive most-recent scans containing it.
	Streak int `json:"streak"`
	// Class is chronic, intermittent, or transient.
	Class string `json:"class"`
	// Acked reports whether the finding has an active acknowledgement.
	Acked bool `json:"acked"`
}

FindingPersistence summarizes how often one finding recurred across a window of scans. It is an implicit, unsupervised severity signal: a critical that recurs in every scan is a standing problem, while one seen once is likely noise or an already-resolved blip.

func ComputePersistence added in v0.8.0

func ComputePersistence(series [][]Finding, acked map[string]bool) []FindingPersistence

ComputePersistence classifies each distinct finding by how often it recurred across series, a slice of per-scan finding sets ordered oldest to newest. acked holds the fingerprints with an active acknowledgement. Results are sorted most-persistent first, then by severity, cluster, and title, so the standing problems lead. An empty series yields no results.

type Fix added in v0.10.0

type Fix struct {
	// Cluster is the cluster to change.
	Cluster string `json:"cluster"`
	// Scanner is the scanner whose field changes.
	Scanner string `json:"scanner"`
	// Field is the field to change, as distributions name it.
	Field string `json:"field"`
	// Kind is one of the Fix constants. Empty picks the one that matches the
	// field: median for numeric, mode for string, consensus for set.
	Kind string `json:"kind,omitempty"`
	// Scope is "fleet" or a cohort name, the norm to fix toward. Empty means
	// the cluster's tagged cohort when that cohort has a norm for the field,
	// since that is the norm the cluster answers to, and the fleet otherwise.
	Scope string `json:"scope,omitempty"`
}

Fix describes one hypothetical change to one cluster.

type FlagChange added in v0.10.0

type FlagChange struct {
	// Scanner is the scanner that reported the field.
	Scanner string `json:"scanner"`
	// Field is the field name.
	Field string `json:"field"`
	// Scan is the ID of the scan where the flag last changed to its final
	// state.
	Scan string `json:"scan"`
}

FlagChange is one outlier flag that changed state over the ledger.

type FleetScore

type FleetScore struct {
	// Score is the rounded 0-100 health score.
	Score int `json:"score"`
	// Grade is a single-letter rollup of Score: A 90+, B 80+, C 70+, D 60+, F under 60.
	Grade string `json:"grade"`
	// Headline is a one-line plain-English summary suitable for a hero card.
	Headline string `json:"headline"`
	// Drivers names the top three factors pulling the score below 100, in
	// descending order of impact. Empty when the fleet is at 100.
	Drivers []FleetScoreDriver `json:"drivers,omitempty"`
	// SampleSize is the number of clusters that contributed scanner data to
	// this score. A score from six clusters and one from sixty are different
	// claims, so the sample travels with the number.
	SampleSize int `json:"sample_size"`
	// Dimensions is the number of scan dimensions that contributed data from
	// at least one cluster.
	Dimensions int `json:"dimensions"`
	// ExcludedClusters is how many scanned clusters contributed no usable data
	// and were left out of the statistical population.
	ExcludedClusters int `json:"excluded_clusters"`
}

FleetScore is a single 0-100 indicator of overall fleet health, suitable for a status-TV hero number on the dashboard. The score is intentionally asymmetric: clusters in trouble pull it down faster than warnings do, and individual findings are capped so a noisy fleet does not collapse to zero on volume alone. The Drivers list names the top reasons the score sits where it does so operators can read the number and the explanation in one glance.

func ComputeFleetScore

func ComputeFleetScore(r *Report) FleetScore

ComputeFleetScore returns a FleetScore derived from a built Report. The function is pure: callers can compare scores across scans simply by calling it on two different reports. Safe to call on nil; returns a perfect score with a placeholder headline.

type FleetScoreDriver

type FleetScoreDriver struct {
	// Reason is a short human-readable label.
	Reason string `json:"reason"`
	// Impact is the number of points (whole or fractional, rounded) this
	// driver removed from the score.
	Impact int `json:"impact"`
}

FleetScoreDriver describes a single factor reducing the fleet score.

type FleetScoreForecast

type FleetScoreForecast struct {
	// Predicted is the projected Fleet Score at PredictedFor.
	Predicted int `json:"predicted"`
	// Lower is the lower bound of a 95% prediction interval, clamped to 0-100.
	Lower int `json:"lower"`
	// Upper is the upper bound of the 95% prediction interval, clamped to 0-100.
	Upper int `json:"upper"`
	// PredictedFor is the wall-clock time the forecast targets. The handler
	// chooses this (typically "now + median inter-scan gap"); the math is
	// time-aware so a quoted ETA reflects the actual extrapolation.
	PredictedFor time.Time `json:"predicted_for"`
	// Slope is the slope of the fitted line in points per hour. Negative means
	// the fleet is degrading.
	Slope float64 `json:"slope_per_hour"`
	// RSquared is the coefficient of determination for the fit.
	RSquared float64 `json:"r_squared"`
	// Basis is the number of historical points used in the fit.
	Basis int `json:"basis"`
	// Sufficient is true when the fit cleared the minimum sample and noise
	// thresholds and the forecast is worth showing.
	Sufficient bool `json:"sufficient"`
	// Headline is a one-line plain-English summary suitable for the dashboard.
	Headline string `json:"headline"`
}

FleetScoreForecast is the predicted next Fleet Score with an uncertainty band derived from the regression's standard error. When the input is too sparse or too noisy to be meaningful, Sufficient is false and callers should hide the forecast in the UI.

func ForecastFleetScore

func ForecastFleetScore(history []FleetScoreHistoryPoint, predictedFor time.Time) FleetScoreForecast

ForecastFleetScore fits an OLS line to the given history (oldest first or any order; the function sorts by timestamp) and returns a forecast for `predictedFor`. If predictedFor is the zero time the function picks a sensible target one median scan interval into the future.

type FleetScoreHistoryPoint

type FleetScoreHistoryPoint struct {
	// ScanID is the scan that produced the score.
	ScanID string `json:"scan_id"`
	// Timestamp is when the scan executed.
	Timestamp time.Time `json:"timestamp"`
	// Score is the Fleet Score for that scan.
	Score int `json:"score"`
}

FleetScoreHistoryPoint is one observation in a Fleet Score time series.

type FleetTrend

type FleetTrend struct {
	// Scanner is the scanner name.
	Scanner string `json:"scanner"`
	// Field is the metric field name.
	Field string `json:"field"`
	// Direction indicates the overall trend.
	Direction TrendDirection `json:"direction"`
	// Points are fleet-aggregated values over time (e.g. count of unique versions).
	Points []TrendPoint `json:"points"`
	// Confidence describes how trustworthy this direction is.
	Confidence string `json:"confidence,omitempty"`
	// RSquared is the coefficient of determination for the fitted slope.
	RSquared float64 `json:"r_squared,omitempty"`
}

FleetTrend tracks a fleet-wide metric over time.

func ComputeFleetTrends

func ComputeFleetTrends(scans []ScanMeta, allResults map[string]map[string]map[string]any) []FleetTrend

ComputeFleetTrends analyzes how fleet-wide uniformity changes over time for string fields like version. It counts unique values per scan.

type Incident added in v0.4.0

type Incident struct {
	// Cluster is the cluster the incident is on.
	Cluster string `json:"cluster"`
	// Title is a synthesized one-line summary.
	Title string `json:"title"`
	// Theme is the root-cause family the fused findings belong to.
	Theme string `json:"theme"`
	// Severity is the highest severity among the member findings.
	Severity string `json:"severity"`
	// Summary is a deterministic, templated root-cause hypothesis.
	Summary string `json:"summary"`
	// Findings lists the titles of the fused member findings, sorted.
	Findings []string `json:"findings"`
}

Incident groups findings that share a likely root cause on one cluster, so an operator reads one incident instead of a flat list of symptoms. A failing admission webhook, an expiring cert, and a deprecated-API rejection are one incident, not three unrelated warnings.

func FuseIncidents added in v0.4.0

func FuseIncidents(findings []Finding) []Incident

FuseIncidents groups findings by cluster and root-cause theme, emitting an incident wherever two or more findings on a cluster share a theme. Findings with no theme, or that stand alone, are left as ordinary findings.

type Ledger added in v0.10.0

type Ledger struct {
	// Scans lists the scan IDs the ledger spans, oldest first.
	Scans []string `json:"scans"`
	// Clusters lists the clusters that drifted, largest change in deviation
	// mass first.
	Clusters []ClusterDrift `json:"clusters"`
}

Ledger records which clusters drifted across a run of scans and why: how their deviation mass moved, which of their values changed, and which outlier flags they gained or lost, read from the fleet-scope distributions. A cluster drifted when one of its own values changed or a flag flipped; a cluster whose z moved only because the norm moved under it is left out.

func DriftLedger added in v0.10.0

func DriftLedger(scans []ScanReport) Ledger

DriftLedger builds the ledger over scans, which must be ordered oldest first. A single scan or none yields a ledger with no drift.

type MisCohortFinding

type MisCohortFinding struct {
	// Cluster is the cluster whose tag disagrees with its profile.
	Cluster string `json:"cluster"`
	// TaggedAs is the user-applied cohort tag the cluster carries.
	TaggedAs string `json:"tagged_as"`
	// ProfileMatches is the cohort tag a majority of similar clusters carry.
	ProfileMatches string `json:"profile_matches"`
}

MisCohortFinding describes a cluster whose user-applied cohort tag does not match where its scanner profile places it. This catches mislabeled clusters that survived because every existing tool only looks at one cluster at a time.

type NormBasis added in v0.10.0

type NormBasis struct {
	// Scope is "fleet" or the name of the cohort the norm was computed over.
	Scope string `json:"scope"`
	// Kind is "numeric", "string", or "set".
	Kind string `json:"kind"`
	// Median is the population median of a numeric field.
	Median float64 `json:"median"`
	// MAD is the median absolute deviation of a numeric field from Median.
	MAD float64 `json:"mad"`
	// Mode is the most common value of a string field.
	Mode string `json:"mode,omitempty"`
	// ModeShare is the fraction of the population holding Mode.
	ModeShare float64 `json:"mode_share,omitempty"`
	// Consensus lists the members of a set field present in at least
	// Threshold of the population, sorted.
	Consensus []string `json:"consensus,omitempty"`
	// Missing lists the consensus members this cluster lacks, sorted.
	Missing []string `json:"missing,omitempty"`
	// SampleSize is the number of clusters in the population.
	SampleSize int `json:"sample_size"`
	// Threshold is the cutoff the detector applied: the modified z-score for
	// numeric, the minimum mode share for string, and the minimum presence
	// share for set membership.
	Threshold float64 `json:"threshold"`
	// Value is this cluster's value for the field.
	Value string `json:"value"`
	// Z is this cluster's signed modified z-score for a numeric field.
	Z float64 `json:"z"`
	// LimitedSample marks a norm drawn from a population too small for MAD.
	LimitedSample bool `json:"limited_sample"`
	// NoWhatIf explains why no what-if can project a fix toward this norm.
	// Empty when one can.
	NoWhatIf string `json:"no_what_if,omitempty"`
}

NormBasis states the norm a fleet-relative finding was measured against, so a reader can check the claim without rerunning the statistics. Which fields apply depends on Kind. Absolute-rule findings carry no NormBasis.

type Outcome added in v0.10.0

type Outcome struct {
	// Fix is the change, with Kind and Scope resolved.
	Fix Fix `json:"fix"`
	// ScoreBefore is the fleet score as scanned.
	ScoreBefore int `json:"score_before"`
	// ScoreAfter is the fleet score after the fix.
	ScoreAfter int `json:"score_after"`
	// Before is the field's distribution as scanned.
	Before *Distribution `json:"before"`
	// After is the field's distribution after the fix, nil when every
	// cluster now agrees or the population no longer qualifies for one.
	After *Distribution `json:"after,omitempty"`
	// MedianShift is the change in the numeric median.
	MedianShift float64 `json:"median_shift"`
	// MADShift is the change in the numeric MAD.
	MADShift float64 `json:"mad_shift"`
	// NewOutliers lists clusters flagged on the field after the fix that were
	// not flagged before, sorted.
	NewOutliers []string `json:"new_outliers,omitempty"`
	// ClearedOutliers lists clusters flagged before that are not flagged
	// after, sorted.
	ClearedOutliers []string `json:"cleared_outliers,omitempty"`
}

Outcome is what the fleet would look like after a Fix. Nothing is written: the report is rebuilt from a patched copy of the scan results.

func WhatIf added in v0.10.0

func WhatIf(clusters []string, results map[string]map[string]scanner.Result, opts BuildOptions, fix Fix) (Outcome, error)

WhatIf builds the report for clusters and results, applies fix to a copy of the results, rebuilds, and returns how the score and the field's norm move. results is not modified.

type OutlierResult

type OutlierResult struct {
	// Cluster is the cluster that deviates.
	Cluster string `json:"cluster"`
	// Field is the data field that deviates.
	Field string `json:"field"`
	// Value is the cluster's value for the field.
	Value string `json:"value"`
	// FleetNorm is the typical fleet value (median for numeric, mode for string).
	FleetNorm string `json:"fleet_norm"`
	// Deviation is the modified z-score for numeric fields.
	Deviation float64 `json:"deviation,omitempty"`
	// Scanner is the scanner that produced this data.
	Scanner string `json:"scanner"`
	// Severity is critical, warning, or info.
	Severity string `json:"severity"`
	// Scope is "fleet" or the name of the cohort the norm was computed over.
	Scope string `json:"scope"`
	// Kind is "numeric", "string", or "set".
	Kind string `json:"kind"`
	// Median is the population median of a numeric field.
	Median float64 `json:"median"`
	// MAD is the median absolute deviation of a numeric field from Median.
	MAD float64 `json:"mad"`
	// Mode is the most common value of a string field.
	Mode string `json:"mode,omitempty"`
	// ModeShare is the fraction of the population holding Mode.
	ModeShare float64 `json:"mode_share,omitempty"`
	// Consensus lists the consensus members of a set field, sorted.
	Consensus []string `json:"consensus,omitempty"`
	// Missing lists the consensus members this cluster lacks, sorted.
	Missing []string `json:"missing,omitempty"`
	// SampleSize is the number of clusters in the population.
	SampleSize int `json:"sample_size"`
	// Threshold is the cutoff the detector applied (see NormBasis.Threshold).
	Threshold float64 `json:"threshold"`
	// Z is the signed modified z-score for a numeric field.
	Z float64 `json:"z"`
	// ExplainedBy names the operator-tagged cohort whose own norm holds this
	// fleet outlier, empty otherwise. An explained outlier raises no finding.
	ExplainedBy string `json:"explained_by,omitempty"`
	// LimitedSample marks a norm drawn from a population too small for MAD.
	LimitedSample bool `json:"limited_sample"`
}

OutlierResult describes a cluster that deviates from the fleet norm.

func DetectOutliers

func DetectOutliers(r *Report, threshold float64) []OutlierResult

DetectOutliers analyzes a report and returns clusters that deviate from fleet norms. The threshold controls sensitivity for numeric fields: lower values flag more outliers. The function only emits findings when the sample is large enough to be statistically meaningful (see minMADSample).

func (OutlierResult) Basis added in v0.10.0

func (o OutlierResult) Basis() *NormBasis

Basis returns the norm this outlier was measured against. A finding built from an outlier takes its NormBasis from here, so the two never disagree.

type Remediation

type Remediation struct {
	// Command is a kubectl or related command that addresses the finding.
	Command string `json:"command,omitempty"`
	// YAML is a manifest snippet the operator can apply directly.
	YAML string `json:"yaml,omitempty"`
	// RunbookURL is an optional runbook link.
	RunbookURL string `json:"runbook_url,omitempty"`
}

Remediation describes the concrete action an operator can take to resolve a finding. Command is a kubectl invocation parameterized with the actual offending resource names so the user does not have to re-discover them. YAML, when present, is a multi-line baseline manifest that satisfies the requirement (for example a default-deny NetworkPolicy or a ResourceQuota). RunbookURL points at an internal runbook the operator has wired up; it is optional and may be empty.

type Report

type Report struct {
	// Timestamp is when the scan was executed.
	Timestamp string `json:"timestamp"`
	// Clusters lists the kubeconfig contexts that were scanned.
	Clusters []string `json:"clusters"`
	// Categories groups sections by functional area.
	Categories []CategoryReport `json:"categories"`
	// Sections holds per-scanner comparison results keyed by scanner name.
	Sections map[string]*SectionReport `json:"sections"`
	// Summary holds fleet-wide summary statistics.
	Summary Summary `json:"summary"`
	// Findings lists human-readable issues discovered across the fleet.
	Findings []Finding `json:"findings"`
	// ClusterHealths holds per-cluster health summaries.
	ClusterHealths []ClusterHealth `json:"cluster_healths"`
	// Outliers lists clusters that deviate from fleet norms. Fleets of
	// minMADSample clusters or more use MAD. Fleets of minMajoritySample to
	// minMADSample-1 use the leave-one-out majority diff, whose results are
	// marked LimitedSample. Smaller fleets get none and a line in Notes.
	Outliers []OutlierResult `json:"outliers,omitempty"`
	// Distributions holds the population behind every field the fleet and
	// cohort detectors evaluated, fleet scope first, most deviant first.
	Distributions []Distribution `json:"distributions,omitempty"`
	// DeviationMass sums, per cluster, the capped |z| of its numeric values
	// across the fleet-scope distributions. Clusters at zero are left out.
	DeviationMass map[string]float64 `json:"deviation_mass,omitempty"`
	// ScoreAttribution is the full cost ledger behind FleetScore.
	ScoreAttribution *ScoreAttribution `json:"score_attribution,omitempty"`
	// Capacity holds smart capacity analysis per cluster, correlating
	// utilization, pressure, events, and headroom.
	Capacity []CapacityAnalysis `json:"capacity,omitempty"`
	// FleetScore is the single 0-100 indicator of overall fleet health,
	// suitable as a hero number on a status TV. Populated by Build.
	FleetScore FleetScore `json:"fleet_score"`
	// Cohorts partitions the fleet into groups of similar clusters and
	// reports within-cohort outliers. Cohorts come from user-supplied
	// cluster tags when present, otherwise from agglomerative clustering
	// on scanner-derived features.
	Cohorts []CohortSummary `json:"cohorts,omitempty"`
	// Degraded lists scanner runs that did not return complete, trustworthy
	// data for a cluster, so the report surfaces reduced coverage instead of
	// reading a failed or forbidden scan as a clean, zero-resource result.
	Degraded []ScannerStatus `json:"degraded,omitempty"`
	// Incidents groups related findings by shared root cause so operators read
	// one incident instead of a flat list of correlated symptoms.
	Incidents []Incident `json:"incidents,omitempty"`
	// Brief is a short, deterministic executive summary of this report.
	Brief Brief `json:"brief"`
	// Notes explain analysis the report could not run, such as outlier
	// detection on a fleet too small to have a norm.
	Notes []string `json:"notes,omitempty"`
}

Report is the top-level output structure.

func Build

func Build(clusters []string, results map[string]map[string]scanner.Result, opts ...BuildOptions) *Report

Build creates a Report from per-cluster scanner results. The results map is keyed by cluster name, then by scanner name. Options are optional; when omitted, defaults are used.

func (*Report) DegradedByCluster added in v0.2.0

func (r *Report) DegradedByCluster() map[string]int

DegradedByCluster counts, per cluster, how many scanner runs did not return complete, trustworthy data. Clusters with full coverage are omitted. Returns nil when the whole fleet scanned cleanly.

type ScanMeta

type ScanMeta struct {
	// ID is the scan identifier.
	ID string
	// Timestamp is when the scan ran.
	Timestamp time.Time
}

ScanMeta identifies a scan for trend analysis without depending on the store package.

type ScanReport added in v0.10.0

type ScanReport struct {
	// ID is the scan ID. It may be empty when the report was not stored.
	ID string
	// Report is the report built from the scan.
	Report *Report
}

ScanReport pairs a report with the ID of the scan it was built from.

type ScannerInfo added in v0.10.0

type ScannerInfo struct {
	// ID is the scanner key used in reports and findings.
	ID string `json:"id"`
	// Label is the human-readable name.
	Label string `json:"label"`
	// Description says in one line what the scanner checks.
	Description string `json:"description"`
	// Category is the report category the scanner belongs to.
	Category string `json:"category"`
}

ScannerInfo describes one scanner for clients that list them.

func ScannerCatalog added in v0.10.0

func ScannerCatalog() []ScannerInfo

ScannerCatalog returns every scanner with a label, sorted by ID.

type ScannerStatus added in v0.2.0

type ScannerStatus struct {
	// Cluster is the cluster the scanner ran against.
	Cluster string `json:"cluster"`
	// Scanner is the scanner name.
	Scanner string `json:"scanner"`
	// State is "degraded", "errored", or "unavailable".
	State string `json:"state"`
	// Reason is a short explanation of why the data is not fully trustworthy.
	Reason string `json:"reason,omitempty"`
}

ScannerStatus records a scanner run that did not return complete, trustworthy data for one cluster. It lets the report show degraded coverage ("3 of 24 scanners degraded on cluster X") instead of silently treating a failed or forbidden scan as a clean result.

type ScoreAttribution added in v0.10.0

type ScoreAttribution struct {
	// Terms lists every term that cost points, in a fixed order.
	Terms []ScoreTerm `json:"terms"`
	// Entries lists every contribution to a term, grouped by term, rule, and
	// cluster.
	Entries []ScoreEntry `json:"entries"`
	// Total is the sum of every term's applied cost.
	Total float64 `json:"total"`
	// ByCluster sums applied cost per cluster. Fleet-wide costs are left out.
	ByCluster map[string]float64 `json:"by_cluster,omitempty"`
	// ByRule sums applied cost per finding rule. Costs not tied to a rule are
	// left out.
	ByRule map[string]float64 `json:"by_rule,omitempty"`
}

ScoreAttribution is the full cost ledger behind a FleetScore: every term with its raw and capped cost, and every finding or cluster that contributed to a term. The applied costs sum to Total, and the score is 100 - Total rounded and floored at zero.

func ComputeScoreAttribution added in v0.10.0

func ComputeScoreAttribution(r *Report) ScoreAttribution

ComputeScoreAttribution returns the cost ledger for r. Safe to call on nil; returns an empty ledger.

func (ScoreAttribution) Drivers added in v0.10.0

func (a ScoreAttribution) Drivers() []FleetScoreDriver

Drivers returns the top three terms by rounded cost, ties kept in term order.

type ScoreEntry added in v0.10.0

type ScoreEntry struct {
	// Term is the category the entry belongs to.
	Term string `json:"term"`
	// Rule is the finding rule charged, empty for costs not tied to a rule.
	Rule string `json:"rule,omitempty"`
	// Cluster is the cluster charged, empty for fleet-wide costs.
	Cluster string `json:"cluster,omitempty"`
	// Raw is the cost before the term's cap.
	Raw float64 `json:"raw"`
	// Cost is the cost after the term's cap.
	Cost float64 `json:"cost"`
}

ScoreEntry is one contribution to a term. When a term is capped every entry is scaled by the same factor, so entries of a term sum to its Cost.

type ScoreTerm added in v0.10.0

type ScoreTerm struct {
	// Term is the category, one of the Term constants.
	Term string `json:"term"`
	// Reason is the driver label for this term.
	Reason string `json:"reason"`
	// Count is how many findings or clusters the term charged.
	Count int `json:"count"`
	// Raw is the cost before the cap.
	Raw float64 `json:"raw"`
	// Cap is the most the term may cost, zero when uncapped.
	Cap float64 `json:"cap,omitempty"`
	// Cost is the points the term removed from the score.
	Cost float64 `json:"cost"`
}

ScoreTerm is one cost category of the fleet score.

type SectionReport

type SectionReport struct {
	// Uniform is true when all clusters produced identical data for this scanner.
	Uniform bool `json:"uniform"`
	// PerCluster holds the raw scanner data from each cluster.
	PerCluster map[string]any `json:"per_cluster"`
	// Divergences describes specific differences found between clusters.
	Divergences []Divergence `json:"divergences,omitempty"`
}

SectionReport holds comparison data for one scanner across all clusters.

type SelfDrift added in v0.3.0

type SelfDrift struct {
	// Cluster is the cluster that drifted from its own past.
	Cluster string `json:"cluster"`
	// Scanner is the scanner that produces the metric.
	Scanner string `json:"scanner"`
	// Field is the metric field name.
	Field string `json:"field"`
	// Baseline is the median of the cluster's history before the change point.
	Baseline float64 `json:"baseline"`
	// Latest is the mean of the cluster's values after the change point.
	Latest float64 `json:"latest"`
	// Deviation is the modified z-score of Latest against the baseline, or the
	// fractional step when the baseline was flat.
	Deviation float64 `json:"deviation"`
	// ChangedAt is when the shift began, taken from the change-point scan.
	ChangedAt time.Time `json:"changed_at"`
	// Direction is worsening or improving, calibrated per metric.
	Direction TrendDirection `json:"direction"`
	// Severity is warning for a worsening shift, info for an improving one.
	Severity string `json:"severity"`
}

SelfDrift describes a cluster whose own metric shifted away from its history. The cross-cluster view cannot see this because the cluster may still look normal next to its peers while having moved sharply from its own past.

func DetectSelfDrift added in v0.3.0

func DetectSelfDrift(cluster string, scans []ScanMeta, resultsByScan map[string]map[string]map[string]any) []SelfDrift

DetectSelfDrift finds metrics where one cluster diverged from its own history. It mirrors ComputeClusterTrends' inputs: resultsByScan is keyed by scan ID, then scanner name, then field. For each tracked metric it locates the single most likely change point in the cluster's time series and flags a shift whose recent segment is far from the cluster's own pre-change median and MAD.

type Summary

type Summary struct {
	// ClusterCount is the number of clusters scanned.
	ClusterCount int `json:"cluster_count"`
	// ScannerCount is the number of scanners executed.
	ScannerCount int `json:"scanner_count"`
	// UniformCount is how many scanners found identical data across all clusters.
	UniformCount int `json:"uniform_count"`
	// DivergentCount is how many scanners found differences.
	DivergentCount int `json:"divergent_count"`
	// TotalDivergences is the total number of individual divergence points.
	TotalDivergences int `json:"total_divergences"`
	// CriticalCount is the number of critical-severity divergences.
	CriticalCount int `json:"critical_count"`
	// WarningCount is the number of warning-severity divergences.
	WarningCount int `json:"warning_count"`
}

Summary holds fleet-wide statistics.

type TrendDirection

type TrendDirection string

TrendDirection indicates whether a metric is improving, worsening, or stable.

const (
	// TrendStable indicates no significant change.
	TrendStable TrendDirection = "stable"
	// TrendImproving indicates the metric is getting better.
	TrendImproving TrendDirection = "improving"
	// TrendWorsening indicates the metric is getting worse.
	TrendWorsening TrendDirection = "worsening"
)

type TrendPoint

type TrendPoint struct {
	// Timestamp is when this value was observed.
	Timestamp time.Time `json:"timestamp"`
	// ScanID is the scan that produced this value.
	ScanID string `json:"scan_id"`
	// Value is the metric value.
	Value float64 `json:"value"`
}

TrendPoint represents a metric value at a point in time.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL