Add a monitor
In kwatch a monitor is a detector. kwatch always watches every supported
resource; there are no per-resource monitor switches, so a new detector needs
no configuration key. Which edition a detector belongs to (Community or Pro) is
decided in the feature catalog (deploy/feature-catalog.tsv and
internal/feature); a new detector must be added there with its tier.
A detector reads one object and returns its health. It never reads
Kubernetes, a clock or a client. Real example: detectors.HPA in
internal/detection/detectors/hpa.go, which reports an autoscaler that
cannot read its metrics.
- Name the reason. Add a constant to
internal/detection/reasons/reasons.go, as inHPAMaxedOut = "HPAMaxedOut". Reasons are stable: people grep audit logs and route alerts by them. - Give it a mode. Add the reason to the area map in
internal/detection/health.gothat fits it (containerModes,workloadModes,nodeModes, ...).modeByReasonmerges those maps; HPA reasons live inworkloadModes, as inreasons.HPAMaxedOut: ModeScalingMaxedOut. Every mode is adetection.Modeconstant ininternal/detection/mode_names.go; a new mode gets its constant there first. The mode is the short failure identity that the propagation table matches and the tracker compares. Modes nest by dots:ImagePull.Registryis a kind ofImagePull. A detector gets Health from severity (InfoandWarningbecome degraded,Criticalbecomes failing); setHealthon the finding only when that is wrong, for exampledetection.Unknown. - Write the detector. Implement
Name,KindsandDetectininternal/detection/detectors/. Read attributes with the helpers (number,text,condition), returndetection.Finding{Reason, Severity, Since, Summary, Evidence}, and let the registry fill in the entity, health and mode. A finding that is advice about configuration rather than a failure (seedetectors.Risk) setsAdvisory: trueandSeverity: detection.Info: it is never announced, never opens an incident on its own and never leads a message about a failure; a failure of the same workload may quote it as a consequence. For a condition that depends on elapsed time callctx.RecheckAfter(d). When the kind may be unwatched, checkctx.Synced(kind)and return nothing: a missing source is never a recovery. Evidence is text people read; it is redacted when first recorded. - Register it. Add the detector, for example
HPA{}, to the list inDefault()ininternal/detection/detectors/default.go. The application and every test harness build their registry fromDefault(), so this one line enables it everywhere. If the detector needs a new attribute, add the attribute name and its extraction to the kind's schema ininternal/inventory/kube(see the source guide). - Choose its delivery tier. The incident manager decides how loudly a
finding is delivered in
internal/incident/policy.go. A finding worth knowing but never worth an interruption on its own (likeServiceUnused) belongs indigestReasons. A finding that fires before anything failed but is a real cause of the failures that follow (likeHPAMaxedOutorVolumeUsageHigh) belongs incauseOnlyReasons: an incident made only of such findings is silent, and is announced once a real failure joins it. A critical finding that should page, not just notify, needs a rule inpageRulesininternal/incident/page_rules.go. Everything else notifies by severity. - Test it. Add a table test beside the detector, like
TestHPAConditionReasonsininternal/detection/detectors/hpa_test.go: build a model withnewTestModel()(fromfixtures_test.go), set attributes, callDetect, and assert reasons for the positive, negative (healthy and unrelated) and unknown cases. The shared guards fail if a step is missing:TestEveryReasonHasMode(step 2, ininternal/detection/health_test.go) andTestDefaultDetectorsAreDistinct(step 4, indefault_test.go).
go test ./internal/detection/... \
-run 'TestHPA|TestEveryReasonHasMode|TestDefaultDetectorsAreDistinct'
make verify-focused PKGS="./internal/detection/... ./internal/incident"
If the detector introduces a failure that can cause others, add a
propagation row; that guide also adds
the plain-English wording a message uses for the cause (causeWords).
Regenerate Kubernetes coverage with
go run ./cmd/coveragedocs when modes change.
Documentation
Update the matching user-facing page (see Kubernetes failure coverage), mark it as Pro when the feature catalog says so, and add release notes when behavior changes.