Extending the catalog with private checks
This guide is for downstream consumers who want to run their own hardware health checks — proprietary thresholds, vendor-specific conditions, signals the public catalog does not carry — on top of health-validation, without forking it.
The pattern is: keep your consumer code in its own module that imports this one, register your check definitions with the catalog, and supply the signals they read. The snippets below are self-contained; nothing here requires changes to health-validation itself.
What is stable, and what is not
[!IMPORTANT] Build against the catalog. Treat the toolkit’s sensor/backend layer as a demonstration.
- The catalog — the
catalog/healthvocabulary (CheckDefinition,Rule,ThresholdSpec, theMeasurementinterface, the enums) and thecatalogregistry/evaluation engine — is the extension surface. Your check definitions are data written against it.- The toolkit sensor and backend implementations (the NVML/sysfs/journal sensors, the
toolkit.Collectpipeline, theapp.Appbuilder, and the probe seams shown below) are reference implementations for demonstration and are subject to change. They are functional today, but do not treat their API as stable; expect the collection wiring to churn.
The practical consequence: put your judgment (what condition is unhealthy, at what threshold, with what remediation) in check definitions, which are durable. Keep your dependence on the collection wiring thin and localized so it is cheap to follow when it changes.
A check is data
A check is a health.CheckDefinition — no code, no hardware dependency. The
engine evaluates its CEL Rules against signal values a measurement supplies.
Fields worth knowing:
CategoryandRequiredSignalsare the routing key — see below.Rulesare CEL expressions over the union of the measurement’s signal names and the check’s threshold names. The worst-severity matching rule sets the verdict;${name}placeholders interpolate signal and threshold values.Thresholdsare operator-tunable by default.FixedThresholdvalues can never be overridden;RequiredOverridemeans the check skips until a fleet supplies a value.RecommendedAction’sRemediationActionsis an open[]RemediationAction(a string type, not a closed enum): use thehealth.RemediationAction*constants for steps NVIDIA’s own catalog uses, or supply your own string for a remediation step specific to your extension. A downstream remediation engine should treat an unrecognized value as an opaque step, not an error.Provenance,MaturityLevel, and the other classification fields are passed through untouched. Usehealth.Private.ConditionScoperecords how far the detected condition reaches:NodeScope,DomainScope, orClusterScope. It isNodeScopewhen omitted, so an existing check keeps its current meaning without change. Unlike the fields above it is not passed through untouched: a collection-failure verdict reportsNodeScopewhatever the check declares.
Routing: how a measurement reaches your check
The engine feeds a measurement to a check when both hold:
- they share a
Category, and - the measurement’s schema carries every one of the check’s
RequiredSignals.
Your check is then evaluated once per matching measurement (so a per-GPU check
yields one verdict per GPU). If no supplied measurement carries its required
signals, the check is skipped NO_APPLICABLE_MEASUREMENT; if its whole category
supplied no measurement at all, it is skipped SENSOR_MISSING. Registering the
check is what declares that its sensor is expected, so every registered check
produces a verdict — a result or a skip — and none disappears from the output.
So a private check only does something if some measurement in its category carries its signals. That is the real work of extending: getting your signals onto a measurement.
Two invariants keep routing unambiguous. Enforce both in your tests:
- Signal names are unique within a category. Two measurement types in the
same category must not share a signal name, or a check could match the wrong
one. Assert with
catalog.CheckSignalUniqueness(measurements...). - Every check has a provider. A check whose signals no measurement carries
can never run. Assert with
cat.CheckCoverage(measurements...).
Getting your signals collected (demonstration wiring — subject to change)
The mechanics in this section use the toolkit’s sensor/backend layer, which is demonstration-grade and subject to change. Keep this wiring thin.
There are two situations.
1. Your signals come from a domain the toolkit already enumerates (e.g. GPU
via NVML). Attach a prober to the shared backend so the existing
category-measurement carries your extra signals. A prober implements
nvml.DeviceProber (Measure(ProbeContext) health.Measurement plus an
Errored fallback); it reads per-device state into your own measurement type
whose Category() is health.GPU and whose struct fields (via json tags)
are exactly your check’s RequiredSignals. Install it on the app’s shared NVML
backend:
2. Your signals come from a new source. You would implement collection here, but this is exactly the layer flagged as unstable — approach it knowing the interfaces may move.
Registering and running your checks
The app builder is the demonstration entry point. WithChecks registers your
definitions alongside the built-ins (declared classification honored; a
duplicate ID fails):
If you do not want the node runner at all, use the catalog directly — the most stable path:
A recommended consumer layout
Keep your module thin and mirror the public per-domain structure. For a private GPU vertical, that is roughly:
health-validation owns the NVML lifecycle, device enumeration, and the built-in sensors; your module owns only its measurement, its checks, and its per-device read.
Testing your extension
- Unit-test each check with
catalog.NewEmpty(),Register, thenEvaluate(single check) orEvaluateAll(batch) against measurements you construct directly — no hardware needed. - Assert
CheckSignalUniquenessandCheckCoverageover your full measurement-plus-check set, so a renamed signal or a check with no provider fails a test rather than silently never running.
See Architecture for package roles and the evaluation data flow.