Compatibility
Compatibility
checkfleet gets embedded in things that outlive a release: a config file in a
repo, a jq expression in a pipeline, a Grafana panel, a cron job that has been
green for a year. This page says exactly which of those are safe to depend on.
The short version: anything on this page is stable for the whole 1.x line. If it has to change, it changes through the deprecation policy below — never silently, never in a patch.
Versioning
checkfleet follows semantic versioning on the surfaces listed here.
| Change | Version bump |
|---|---|
| A stable surface changes meaning or disappears | major |
| A new module, output, flag or field | minor |
| A fix that leaves every stable surface intact | patch |
Adding a field to an output is a minor, not a major: consumers are expected to ignore fields they do not know. Removing or repurposing one is a major.
Before 1.0 every release was tagged as a minor or patch regardless, because no stability was promised yet. The guarantees on this page start at 1.0.0.
What is stable
1. The config schema
Every documented key in Configuration — its name, its type, its default, and what it does. A config that works on 1.0 works on every later 1.x without edits.
Two properties are part of the contract, not accidents:
- Unknown keys do not abort the run and do not change the exit code. A config
written for a newer checkfleet runs on an older one, ignoring what it does not
know — that tolerance is the point, and it is why
KnownFieldsis deliberately not enabled. But tolerated is not the same as hidden: every command that acts on a config prints a notice on stderr naming the ignored key and the closest valid name, andvalidateanddoctorreport them as problems. An ignored key means the module never runs, so the run would otherwise report a healthy fleet having checked nothing. ${VAR}interpolation (${VAR},${VAR:-default},${file:/path},$${for a literal) is applied to config values before parsing.
2. The JSON output
--output json emits a document with a top-level schema field. These keys are
stable:
| Key | Type | Meaning |
|---|---|---|
schema |
number | JSON document format version (currently 1) |
findings |
array | every finding, in the documented order |
summary |
object | count per status |
worst |
string | worst status across the findings — this is the field to gate on |
started |
string | RFC 3339 start time of the run |
duration_ns |
number | run duration in nanoseconds |
labels |
object | global labels from the config, omitted when none are set |
And within a finding:
| Key | Type | Meaning |
|---|---|---|
check |
string | module name |
target |
string | what was checked |
status |
string | OK, WARN, BAD or ERROR |
message |
string | human-readable detail — not stable text, see below |
value |
number | optional scalar metric, omitted when the check has none |
unit |
string | unit of value (ms, s, days, bytes, …), omitted with it |
message is stable as a field, not as text. Finding messages get clearer
between releases; matching on their wording is the thing this page cannot
protect. Gate on worst or on status, never on a substring of message.
3. Exit codes
| Code | Meaning |
|---|---|
0 |
the run completed — no gate was set, or nothing reached the threshold |
2 (or --exit-code N) |
the gate tripped |
1 |
systemic failure: unreadable config, unknown module, bad flag |
The load-bearing rule: findings do not fail the run by themselves. A check
that ran and found a problem is a successful check — you decide with
--exit-on when a finding should break the build. A 1 means checkfleet could
not do its job. See CI & pipelines.
Diagnostic commands (validate, doctor, targets, explain, init,
completion, version) never gate: they exit 0 unless something systemic
went wrong. validate is the exception that exits 1 on a config problem —
that is its job.
4. Status semantics
OK → WARN → BAD → ERROR, in that severity order, and ERROR means
checkfleet could not measure (network, handshake, timeout) — not “the target
is unhealthy”. A monitor that treats ERROR as BAD pages someone about a
firewall rule as if the database were down. This distinction will not change.
5. Finding order
Findings are sorted worst-first, then by check, then by target, and the sort is stable. Anyone parsing the text output depends on it, so it is part of the contract. Identical findings (same check, target and status) are deduplicated.
6. Finding identity
A finding is identified by the pair check + target. This is not just
cosmetic: it is the deduplication key for report-issues, for alert, for the
history file and for --baseline. Renaming a module or changing how a module
spells its target makes every open issue reopen and every resolved alert
re-fire, so within 1.x a module keeps its name and its target format.
7. The history and baseline files
--history file is append-only JSONL, one record per run, with a sv schema
version on every line (currently 1). Keys are short because the file grows
forever:
| Record | Key | Meaning |
|---|---|---|
t |
run timestamp, Unix seconds | |
sv |
schema version of the record | |
f |
the run’s entries | |
| Entry | c |
check |
g |
target | |
s |
status | |
v |
optional numeric value | |
u |
unit of v |
Reading is backward and forward compatible by construction: a record written
before sv existed reads as version 1, and a record stamped with a newer
version is skipped and reported rather than misread — a wrong diff is worse than
a loud failure.
--baseline file is a JSON document with its own version field, and a
baseline whose version this binary does not understand is rejected outright with
the command to re-record it.
8. Prometheus metric names
Metric and label names are stable — renaming one breaks a dashboard silently, because the query simply returns no data.
| Metric | Labels | Meaning |
|---|---|---|
checkfleet_finding_status |
check, target + global labels |
severity per finding (0=OK, 1=WARN, 2=BAD, 3=ERROR) |
checkfleet_findings_total |
status + global labels |
count per status |
checkfleet_worst_status |
global labels | worst severity across the fleet |
checkfleet_run_duration_seconds |
global labels | duration of the last run |
checkfleet_last_run_timestamp_seconds |
global labels | when the last run finished |
checkfleet_module_findings |
module |
findings produced per module (serve) |
checkfleet_module_errors |
module |
ERROR findings per module (serve) |
The severity encoding (0/1/2/3) is part of the contract too: alert rules
compare against those numbers.
9. Command and flag names
Documented subcommands and flags keep working. A flag can gain a new value (as
--exit-on-bad became an alias of --exit-on bad) but does not change meaning.
What is not stable
Depending on any of these is fine — just expect it to move in a minor release:
- Finding message wording. Improved freely. Never match on it.
- The Go packages. Everything lives under
internal/, which the Go toolchain itself forbids importing. checkfleet is a tool, not a library. - Text and Markdown layout. Written for humans; columns and section titles
get rearranged. Use
json,csvorjunitfor machines. - The desktop app. Versioned separately and still evolving; the CLI is the source of truth for behaviour.
serveHTML pages (/healthzand/readyzbodies are stable; the human page is not).- The exact text of errors and warnings on stderr.
Deprecation policy
When something stable has to change:
- The old form keeps working for the rest of the 1.x line. No exceptions, including config keys — a config that stops loading after an upgrade is the failure mode this policy exists to prevent.
- It warns. Using a deprecated form prints a notice on stderr naming the replacement. Warnings go to stderr, never into the rendered output, so they cannot corrupt a parsed document or a webhook payload.
- It is documented in the release notes for that version and marked deprecated here, with what replaces it.
- It is removed only in the next major, never in a minor or a patch.
New behaviour that would change existing results arrives opt-in, behind a flag or a config key that defaults to today’s behaviour.
Reporting a compatibility break
If an upgrade broke something on this page, that is a bug, not a policy
decision — open an issue with the two versions and the config or command
involved. Include the output of checkfleet version.
How this page stays true
It is enforced by tests, not by good intentions. The keys, metric names and
schema versions listed here are asserted in
internal/output/contract_test.go and internal/history/contract_test.go, and
those tests also check that everything the renderers emit appears on this
page — so a format change cannot land without this document being updated in
the same commit.