Query Contract Corpus — developer contract
tests/query-corpus/ is an end-to-end correctness suite for the query
engine of a stock netdata daemon. Fixtures enter through the real
streaming protocol and queries use the normal HTTP API. Structured responses
are strictly decoded before semantic checks; expectations are
fixture-derived, source-derived, or explicitly labeled stability/parity pins
under the classes below. tests/query-corpus/README.md describes L0-L11
plus cross-cutting API, options, and weights surfaces.
The suite is a self-contained Go module
(github.com/netdata/netdata/tests/query-corpus). Always run go commands
from inside tests/query-corpus/.
Correctness model — why this suite means something
The founding rule: semantic correctness expectations MUST be derived
independently of engine output. Capturing engine output is allowed only for
an explicitly labeled Class C stability pin; such a pin is not a correctness
oracle. Every check in the corpus belongs to one of three classes, with
different rules:
- Class A — first-principles oracles (the default). Fixtures are literal
Go definitions: charts, dimensions, and explicit
(timestamp, collected value, SN flags) points. Most deterministic
fixtures anchor at fixture.T0; wall-clock-only cases use bounded
envelopes. Expected values come from fixture arithmetic, conservation,
additivity, metadata laws, group-by folds, weights math, and anomaly-bit
counts. New checks MUST be Class A unless the transform genuinely cannot
be derived from first principles.
- Class B — ports of engine algorithms. Some transforms are engine
design decisions, not derivable math.
fixture/sn.go, fixture/tier.go,
fixture/timegroup.go, and fixture/viewpoints.go contain source-derived
ports; they also contain explicitly labeled Class A or contract helpers.
Class B code is written from the C source, never captured from engine
output. Rules:
- A port MUST cite
owner/repo @ commit plus the exact
repository-relative source path and line range it mirrors.
- Every divergence found between a port and the engine MUST be resolved
explicitly: either it is an engine bug (author a case for it) or an engine
quirk adopted into the oracle with a recorded pending ruling in the
manifest/SOW. Silently adjusting a port until the engine passes is the
cardinal sin of this suite ("fit-to-engine") and is prohibited.
- Where a port could hide drift, bound it independently (e.g. L2 pins the
SN quantization error envelope against the ORIGINAL values).
- A port MUST NOT be the oracle for a quantity that obeys an independent
law. Where one exists — conservation, monotonicity, additivity — that
law is derivable, so the check is Class A and the port has nothing to
contribute but the engine's own opinion.
ViewBuckets is a
source-derived model of the default tier-0 point-selection/interpolation
subset exercised by L9, not a port of the complete query executor. It is
appropriate only for those covered level-at-an-instant shapes.
ViewSumVolume is a Class A conservation oracle for volume and is
deliberately independent of that selection model. For every Class B
use, prefer an independent law when one exists.
- Fixtures MUST make the answer exact where the contract is exact. A
conservation check reads a difference, so anything else that moves the total
is noise the check will report as a defect. Above tier 0 the loudest such
source is resolution: when a window edge or a plan seam cuts a stored
record, the part inside can only be estimated from the record's width, and
with varying data that estimate is wrong by a share of the record — which
is what tiers cost, not a defect (see "Tiered rollups are not bugs").
- Grid-align the window so no edge falls inside a record, and where a
boundary is discovered rather than chosen (a rotated tier head), carry a
flat dimension alongside the varying ones: a constant survives rollup
exactly, so its share of a cut record is its exact truth and any
difference left is arithmetic.
- Worked example: CASE-026's seam contract first read 0.49s of data
"missing" across a plan switch, entirely from the seam cutting one
random-valued tier1 record. On the flat dimension the same three windows
answer 7,200,000 to the digit at every zoom. Had the noisy version been
committed, the corpus would have reported accepted rollup as an engine
bug — the mirror image of fit-to-engine, and just as damaging.
- Class C — stability pins and parity checks. Formatter byte pins
(primarily L7 and selected option tests) detect output-contract changes;
parity checks prove internal coherence only. Neither establishes
first-principles correctness. Rules:
- A Class C pin MUST be paired with independent validity checks where
they exist (e.g. "the payload parses as JSON", "values equal the
fixture-derived numbers inside the pinned envelope").
- Updating a pinned byte string requires a justified contract change
(a PR that deliberately changes the output format), never "the test
started failing".
Falsifiability discipline: when an expectation and the engine disagree,
there are exactly two exits — the engine is wrong (the case stays as written
and joins the broken list until it is fixed) or a recorded ruling says the
behavior is intended (the case is rewritten to assert the ruled behavior,
with the quirk documented). There is no third option where the oracle is
quietly bent to match, and none where the disagreement is filed away as
acceptable.
Architecture map
stream/stream.go — the fixture child. Speaks plugins.d over the
streaming socket: CHART/DIMENSION/CLABEL, live samples
(BEGIN2/SET2/END2), v1 paced samples (BEGIN/SET/END), and replication
(RBEGIN/RSET/REND). Protocol words quote-switch per word (qw()):
plugins.d accepts both ' and " delimiters, so ids carrying an
apostrophe ship double-quoted. SET2 sends the value explicitly because
the # shorthand truncates fractional values on the parser side. Exact
parser citations are listed under "Changing oracles, pins, and the
harness."
daemon/daemon.go — the harness. Resolves the binary and source tree as a
pair, boots the stock binary with a scratch run dir, assigns a unique
daemon identity, waits for that identity through the HTTP API, exposes
query helpers and WaitRetention, and uses bounded shutdown.
fixture/ — the fixture model plus source-derived Class B ports and
explicitly labeled Class A or contract helpers.
canon/ — the strict typed json2 decoder and point-column helpers. It
rejects invalid schema indices, widths, labels, field types/ranges, and
non-finite numeric metadata while decoding nullable value/hidden cells.
Query-specific assertion helpers enforce whether those nulls are semantically
valid and carry the required annotations.
*_test.go — layer*.go ladder tests, cross-cutting surface tests, and
per-bug caseNNN_test.go files.
manifest.json + MANIFEST.md — the ledger data and its human-readable
mirror (below). manifest.go embeds and validates the data. Keep all three
in sync in the same commit.
reference-python/ — local-only cross-check implementation. It is NOT
tracked and MUST NOT be committed.
The manifest
Every contract case has an entry in manifest.json; its proves, cloud, and
optional fixed_by fields are mirrored as a row in MANIFEST.md.
- One contract key MUST represent one independently actionable semantic
invariant. Multiple fixtures or inputs MAY share a key when they all prove
that same invariant. Independent claims, such as numeric value correctness
and unit rendering, MUST use separate keys and separately registered test or
subtest scopes. A shared green/red verdict is prohibited because an existing
failure in one claim can hide a new regression in another while the broken
contract roster remains unchanged.
- Register ordinary Go assertions with
trackContract(t, name) before any
operation that may fail or skip. Its cleanup records Error, Fatal, and
Skip.
- Use
assertContract(t, name, held) when the test computes an explicit
contract verdict. Call registerContract(t, name) before shared work, so
an earlier Fatal or Skip leaves the verdict visibly incomplete instead
of letting a default-true accumulator report it green.
- When independent test scopes jointly prove one contract, declare their
names in
ManifestCase.Components and register each with
trackContractComponent. One component passing never substitutes for
another component that did not run.
A broken contract fails. Always. On master, on a feature branch,
whether or not the break is already known.
- The manifest records NO expected outcome. There is no "known broken, and
therefore fine" state, and adding one is prohibited: it makes a broken
query engine report success, and this suite exists to name what is
broken, not to keep a list of exceptions.
- An unfiltered root-package run ends with the deduplicated list of broken
contracts and fails if any manifest contract or required component did
not run. That complete list is the corpus's answer — the open-defect
list produced by measurement rather than by hand.
- A filtered daemon-backed run reports how many contracts were fully evaluated
and never claims the complete corpus holds. The deliberately named
daemon-free harness/unit fast path prints only its Go test result and no
query-contract verdict.
-list also prints no contract verdict.
go test ./... therefore exits non-zero while any contract is broken or
the ledger is incomplete. That is the intended signal, not a problem to
suppress. The corpus is not wired into CI.
A case name is <layer-or-CASE-id>/<slug>; Proves is one sentence a
maintainer can read as the contract claim. A case whose bug is fixed keeps
its test as the regression guard and records FixedBy: "#PR".
Running
- Build the daemon first:
ninja -C build netdata from the repo root. By
default the suite pairs ../../build/netdata with ../../src.
- For another checkout, set both paths:
QUERY_CORPUS_NETDATA=/absolute/path/to/build/netdata QUERY_CORPUS_SRC=/absolute/path/to/src go test .... One-sided overrides
are rejected. The pair is operator-declared provenance; the harness does
not inspect binary build metadata.
- Full suite:
cd tests/query-corpus && go test ./... -count=1. Expect
several minutes; duration is hardware and case-selection dependent.
- One test:
go test -count=1 -run 'TestName' .. Some tests consume a
shared palette authored by an earlier layer; include that fixture-producing
test in the filter or run the full suite when a test reports that its
palette is unavailable. A prerequisite skip is not correctness evidence.
- Keep the daemon run dir for inspection:
QUERY_CORPUS_KEEP=1 (it is
always kept on failure; the path is printed as daemon run dir kept:).
- Capture the verdict honestly:
go test ... ; echo "exit=$?" — piping
through tail masks the exit code.
- Before every push of the corpus branch, run the full suite and compare
the broken list to the previous run. It must not grow, and no case may
break that was holding before. The list being non-empty is expected while
the query engine still has open defects.
Authoring fixtures
- Epoch: all points anchor at
fixture.T0. For update_every > 1,
pre-align the series: base := fixture.T0 - fixture.T0%int64(ue) —
storage keeps pushed timestamps exactly, but views re-grid onto absolute
update_every multiples, so unaligned fixtures make expectations
needlessly hard.
- Host GUIDs:
guid(n) builds a deterministic machine GUID. n MUST
be unique across the whole suite — hosts persist in the shared daemon for
the entire run, so a collision silently cross-contaminates two tests.
Before taking a number, grep -n 'guid(' *_test.go and pick an unused
range; ranges used by loops (e.g. soak attempts) reserve their whole
span.
- Settle discipline: after pushing, block on
td.WaitRetention(...)
before querying. Ordinary helpers keep the connection open through
assertions to isolate storage/query checks from teardown timing. CASE-015
deliberately closes immediately and guards the #23118 delivered-data
drain guarantee; immediate close is no longer documented as data loss.
- Weights fixtures: rrdcontexts stamps retention ~1–2s after chart
creation; weights queries return empty until then. Settle on the
contexts
first_time_t (see weightsSettle), not only on retention.
- Tolerances: exact comparison is the default.
Chart.ValueTolerance
is ONLY for quantization-probing fixtures, with the reason in a comment.
- Tier window alignment:
TierWindows(gran) keys on ABSOLUTE multiples
of the granularity, not on offsets from T0. fixture.T0 % 60 == 20, so
the first tier-1 window ends at T0+40 and a fixture whose shape is
keyed on the sample index straddles two regimes per stored window. Anchor
tier queries at T0+40, and let the oracle — never the fixture's index
arithmetic — say what each window contains.
- Forcing wide-point re-delivery: ask for a view grid FINER than the
stored data (
DataParamsTier(ctx, 1, after, before, buckets, ...) with
buckets a multiple of the stored window count). Each stored point is
then delivered to several buckets, carrying its original start and an
INTERPOLATED value — so any grouping that reads value instead of the
window's own statistics answers differently per bucket. That is the only
way to reach the repeat path from a query, and it is how
CASE-023/tier-wide-point caught a constant window being judged on an
interpolated blend of two windows.
Adding a case
- Author the fixture (Class A first; reach for a Class B oracle only when
the transform requires it).
- Push it (
pushLiveBurst for live bursts; paced v1 or replication where
the ingestion path is the thing under test), settle, query.
- Compute expectations in Go from the fixture definition. Never paste a
number you got from the engine.
- Add one manifest entry and
MANIFEST.md row per independently actionable
semantic invariant, then register each contract at the narrowest
test/subtest scope that proves it. Do not put independent value, units,
metadata, or formatting claims behind one green/red verdict.
- Run the full suite; a new case MUST NOT destabilize existing cases
(watch for GUID collisions and shared-host mutations).
Adding a case for a bug (bug workflow)
A case for a known bug is written exactly like any other case: it states
the CORRECT behavior and fails while the engine gets it wrong. It is not
marked, excused, or inverted — it joins the broken list until the fix
lands, and the broken list is what the corpus is for.
- Reproduce the divergence deterministically in its own
caseNNN_test.go
with a minimal fixture. The check asserts the CORRECT behavior and feeds
the result into assertContract.
- Add the manifest entry with a
Proves sentence stating the contract
precisely (what correct is), not the bug's symptoms.
- Confirm it fails on today's daemon, and that the failure names the real
defect — a case that fails for the wrong reason is worse than none.
- The fix goes in its OWN branch/PR — never mixed into the corpus branch.
- Validate the fix branch against the corpus before opening the PR:
- build the fix branch, save the binary aside;
- from the corpus checkout:
QUERY_CORPUS_NETDATA=<fix-checkout>/build/netdata QUERY_CORPUS_SRC=<fix-checkout>/src go test -count=1 -run '<the case plus neighboring pins>' .
Both paths MUST describe the same operator-declared checkout.
- the case MUST now hold, and every other case that was holding MUST
still hold (zero collateral).
- When the fix merges: rebase the corpus branch onto the merge, record
FixedBy: "#PR", reword the case comment and Proves to describe the
contract in force, run the full suite, push. The case lives on as the
regression guard.
- If the divergence is ruled intended behavior instead: change the case to
assert the ruled behavior, document the quirk in the oracle comment and
the
Proves text, and record the ruling.
Changing oracles, pins, and the harness
- An oracle change MUST cite its justification: the fixture math (Class A)
or the C source being ported (Class B). "It makes the suite pass" is not
a justification — that is fit-to-engine.
- A Class B port correction that changes expected values MUST state which
divergence prompted it and why it is not an engine bug.
- Byte-pins change only with a deliberate output-contract change.
- Determinism: expectations MUST NOT depend on wall-clock time. Tests that
must touch "now" (live edge, relative windows) assert ENVELOPES (bounded
ranges, row-count bounds), not exact values.
- Protocol emitters (
stream/) mirror the parser's actual grammar. At
netdata/netdata @ 043f50ec075441010c1495250871d37a8ac69f8d, the
authoritative surfaces are:
- quote/token splitting:
src/libnetdata/line_splitter/line_splitter.h:34-119;
BEGIN2: src/plugins.d/pluginsd_parser.c:815-960;
SET2: src/plugins.d/pluginsd_parser.c:963-1125;
END2: src/plugins.d/pluginsd_parser.c:1128-1163;
- A/R/E flags:
src/plugins.d/pluginsd_internals.h:461-485;
RBEGIN/RSET/REND:
src/plugins.d/pluginsd_replication.c:112-215,217-280,367-441.
Extend emitters only from the relevant parser path and update the checked
revision/ranges when the port changes.
Known boundaries (extension points, not history)
Deliberately out of scope so far; extending into them is welcome and each
states what it takes:
- KS2 exact tail values: the ks2 weights oracle pins the engine's
special cases; a full KSfbar port would make every ks2 weight exact.
- Natural-points full oracle: natural mode pins count/values and a
two-candidate boundary check; a full oracle needs the natural-mode point
walk ported.
- 64-bit counter wrap: unreachable through the signed text protocol;
needs a different ingestion vector.
- Float collected values on the reset path: the v1 SET path parses
integers; the reset/overflow pins use integer counters only.
points > 86400: the API caps points; oversized-grid behavior is
unpinned.
- Cloud tier: replaying the raw halves of L5/L6 through the real cloud
aggregator is designed but lives outside this repo.
Gotchas
- Run
go commands from tests/query-corpus/ (own module); running from
the repo root fails with "go.mod not found".
- Values print through the engine's number formatter: a stored
22.000000000000004 prints as 22. Compare parsed numbers, not
strings, unless the check IS a byte-pin.
- The default shared daemon serves most tests. Tests that need isolation,
rotation, or restarts boot dedicated daemons; never restart or reconfigure
the default shared daemon from an unrelated test.
- After system library upgrades, rebuild
build/netdata before blaming a
test failure on the suite.
- IDE diagnostics on the Go files can be stale;
go vet ./... is the
authority.
1---2name: tests-query-corpus3description: Developer contract for the query contract corpus (tests/query-corpus) — the black-box correctness suite for the Netdata query engine. Use when running the corpus, adding or extending corpus cases, authoring fixtures, changing an oracle or a byte-pin, adding a case for a query-engine bug, recording the fixing PR after it merges, or validating a query-engine fix branch against the corpus.4---5
6# Query Contract Corpus — developer contract
7
8`tests/query-corpus/` is an end-to-end correctness suite for the query
9engine of a stock `netdata` daemon. Fixtures enter through the real
10streaming protocol and queries use the normal HTTP API. Structured responses
11are strictly decoded before semantic checks; expectations are
12fixture-derived, source-derived, or explicitly labeled stability/parity pins
13under the classes below. `tests/query-corpus/README.md` describes L0-L11
14plus cross-cutting API, options, and weights surfaces.
15
16The suite is a self-contained Go module
17(`github.com/netdata/netdata/tests/query-corpus`). Always run `go` commands
18from inside `tests/query-corpus/`.
19
20## Correctness model — why this suite means something
21
22The founding rule: **semantic correctness expectations MUST be derived
23independently of engine output.** Capturing engine output is allowed only for
24an explicitly labeled Class C stability pin; such a pin is not a correctness
25oracle. Every check in the corpus belongs to one of three classes, with
26different rules:
27
28- **Class A — first-principles oracles (the default).** Fixtures are literal
29 Go definitions: charts, dimensions, and explicit
30 `(timestamp, collected value, SN flags)` points. Most deterministic
31 fixtures anchor at `fixture.T0`; wall-clock-only cases use bounded
32 envelopes. Expected values come from fixture arithmetic, conservation,
33 additivity, metadata laws, group-by folds, weights math, and anomaly-bit
34 counts. New checks MUST be Class A unless the transform genuinely cannot
35 be derived from first principles.
36- **Class B — ports of engine algorithms.** Some transforms are engine
37 design decisions, not derivable math. `fixture/sn.go`, `fixture/tier.go`,
38 `fixture/timegroup.go`, and `fixture/viewpoints.go` contain source-derived
39 ports; they also contain explicitly labeled Class A or contract helpers.
40 Class B code is written from the C source, never captured from engine
41 output. Rules:
42 - A port MUST cite `owner/repo @ commit` plus the exact
43 repository-relative source path and line range it mirrors.
44 - Every divergence found between a port and the engine MUST be resolved
45 explicitly: either it is an engine bug (author a case for it) or an engine
46 quirk adopted into the oracle **with a recorded pending ruling** in the
47 manifest/SOW. Silently adjusting a port until the engine passes is the
48 cardinal sin of this suite ("fit-to-engine") and is prohibited.
49 - Where a port could hide drift, bound it independently (e.g. L2 pins the
50 SN quantization error envelope against the ORIGINAL values).
51 - A port MUST NOT be the oracle for a quantity that obeys an independent
52 law. Where one exists — conservation, monotonicity, additivity — that
53 law is derivable, so the check is Class A and the port has nothing to
54 contribute but the engine's own opinion. `ViewBuckets` is a
55 source-derived model of the default tier-0 point-selection/interpolation
56 subset exercised by L9, not a port of the complete query executor. It is
57 appropriate only for those covered level-at-an-instant shapes.
58 `ViewSumVolume` is a Class A conservation oracle for volume and is
59 deliberately independent of that selection model. For every Class B
60 use, prefer an independent law when one exists.
61- **Fixtures MUST make the answer exact where the contract is exact.** A
62 conservation check reads a difference, so anything else that moves the total
63 is noise the check will report as a defect. Above tier 0 the loudest such
64 source is resolution: when a window edge or a plan seam cuts a stored
65 record, the part inside can only be estimated from the record's width, and
66 with varying data that estimate is wrong by a share of the record — which
67 is what tiers cost, not a defect (see "Tiered rollups are not bugs").
68 - Grid-align the window so no edge falls inside a record, and where a
69 boundary is discovered rather than chosen (a rotated tier head), carry a
70 **flat dimension** alongside the varying ones: a constant survives rollup
71 exactly, so its share of a cut record is its exact truth and any
72 difference left is arithmetic.
73 - Worked example: CASE-026's seam contract first read 0.49s of data
74 "missing" across a plan switch, entirely from the seam cutting one
75 random-valued tier1 record. On the flat dimension the same three windows
76 answer 7,200,000 to the digit at every zoom. Had the noisy version been
77 committed, the corpus would have reported accepted rollup as an engine
78 bug — the mirror image of fit-to-engine, and just as damaging.
79- **Class C — stability pins and parity checks.** Formatter byte pins
80 (primarily L7 and selected option tests) detect output-contract changes;
81 parity checks prove internal coherence only. Neither establishes
82 first-principles correctness. Rules:
83 - A Class C pin MUST be paired with independent validity checks where
84 they exist (e.g. "the payload parses as JSON", "values equal the
85 fixture-derived numbers inside the pinned envelope").
86 - Updating a pinned byte string requires a justified contract change
87 (a PR that deliberately changes the output format), never "the test
88 started failing".
89
90**Falsifiability discipline:** when an expectation and the engine disagree,
91there are exactly two exits — the engine is wrong (the case stays as written
92and joins the broken list until it is fixed) or a recorded ruling says the
93behavior is intended (the case is rewritten to assert the ruled behavior,
94with the quirk documented). There is no third option where the oracle is
95quietly bent to match, and none where the disagreement is filed away as
96acceptable.
97
98## Architecture map
99
100- `stream/stream.go` — the fixture child. Speaks plugins.d over the
101 streaming socket: `CHART`/`DIMENSION`/`CLABEL`, live samples
102 (`BEGIN2/SET2/END2`), v1 paced samples (`BEGIN/SET/END`), and replication
103 (`RBEGIN/RSET/REND`). Protocol words quote-switch per word (`qw()`):
104 plugins.d accepts both `'` and `"` delimiters, so ids carrying an
105 apostrophe ship double-quoted. `SET2` sends the value explicitly because
106 the `#` shorthand truncates fractional values on the parser side. Exact
107 parser citations are listed under "Changing oracles, pins, and the
108 harness."
109- `daemon/daemon.go` — the harness. Resolves the binary and source tree as a
110 pair, boots the stock binary with a scratch run dir, assigns a unique
111 daemon identity, waits for that identity through the HTTP API, exposes
112 query helpers and `WaitRetention`, and uses bounded shutdown.
113- `fixture/` — the fixture model plus source-derived Class B ports and
114 explicitly labeled Class A or contract helpers.
115- `canon/` — the strict typed json2 decoder and point-column helpers. It
116 rejects invalid schema indices, widths, labels, field types/ranges, and
117 non-finite numeric metadata while decoding nullable value/hidden cells.
118 Query-specific assertion helpers enforce whether those nulls are semantically
119 valid and carry the required annotations.
120- `*_test.go` — `layer*.go` ladder tests, cross-cutting surface tests, and
121 per-bug `caseNNN_test.go` files.
122- `manifest.json` + `MANIFEST.md` — the ledger data and its human-readable
123 mirror (below). `manifest.go` embeds and validates the data. Keep all three
124 in sync in the same commit.
125- `reference-python/` — local-only cross-check implementation. It is NOT
126 tracked and MUST NOT be committed.
127
128## The manifest
129
130Every contract case has an entry in `manifest.json`; its `proves`, `cloud`, and
131optional `fixed_by` fields are mirrored as a row in `MANIFEST.md`.
132
133- **One contract key MUST represent one independently actionable semantic
134 invariant.** Multiple fixtures or inputs MAY share a key when they all prove
135 that same invariant. Independent claims, such as numeric value correctness
136 and unit rendering, MUST use separate keys and separately registered test or
137 subtest scopes. A shared green/red verdict is prohibited because an existing
138 failure in one claim can hide a new regression in another while the broken
139 contract roster remains unchanged.
140- Register ordinary Go assertions with `trackContract(t, name)` before any
141 operation that may fail or skip. Its cleanup records `Error`, `Fatal`, and
142 `Skip`.
143- Use `assertContract(t, name, held)` when the test computes an explicit
144 contract verdict. Call `registerContract(t, name)` before shared work, so
145 an earlier `Fatal` or `Skip` leaves the verdict visibly incomplete instead
146 of letting a default-true accumulator report it green.
147- When independent test scopes jointly prove one contract, declare their
148 names in `ManifestCase.Components` and register each with
149 `trackContractComponent`. One component passing never substitutes for
150 another component that did not run.
151
152**A broken contract fails. Always.** On master, on a feature branch,
153whether or not the break is already known.
154
155- The manifest records NO expected outcome. There is no "known broken, and
156 therefore fine" state, and adding one is prohibited: it makes a broken
157 query engine report success, and this suite exists to name what is
158 broken, not to keep a list of exceptions.
159- An unfiltered root-package run ends with the deduplicated list of broken
160 contracts and fails if any manifest contract or required component did
161 not run. **That complete list is the corpus's answer** — the open-defect
162 list produced by measurement rather than by hand.
163- A filtered daemon-backed run reports how many contracts were fully evaluated
164 and never claims the complete corpus holds. The deliberately named
165 daemon-free harness/unit fast path prints only its Go test result and no
166 query-contract verdict. `-list` also prints no contract verdict.
167- `go test ./...` therefore exits non-zero while any contract is broken or
168 the ledger is incomplete. That is the intended signal, not a problem to
169 suppress. The corpus is not wired into CI.
170
171A case name is `<layer-or-CASE-id>/<slug>`; `Proves` is one sentence a
172maintainer can read as the contract claim. A case whose bug is fixed keeps
173its test as the regression guard and records `FixedBy: "#PR"`.
174
175## Running
176
177- Build the daemon first: `ninja -C build netdata` from the repo root. By
178 default the suite pairs `../../build/netdata` with `../../src`.
179- For another checkout, set both paths:
180 `QUERY_CORPUS_NETDATA=/absolute/path/to/build/netdata
181 QUERY_CORPUS_SRC=/absolute/path/to/src go test ...`. One-sided overrides
182 are rejected. The pair is operator-declared provenance; the harness does
183 not inspect binary build metadata.
184- Full suite: `cd tests/query-corpus && go test ./... -count=1`. Expect
185 several minutes; duration is hardware and case-selection dependent.
186- One test: `go test -count=1 -run 'TestName' .`. Some tests consume a
187 shared palette authored by an earlier layer; include that fixture-producing
188 test in the filter or run the full suite when a test reports that its
189 palette is unavailable. A prerequisite skip is not correctness evidence.
190- Keep the daemon run dir for inspection: `QUERY_CORPUS_KEEP=1` (it is
191 always kept on failure; the path is printed as `daemon run dir kept:`).
192- Capture the verdict honestly: `go test ... ; echo "exit=$?"` — piping
193 through `tail` masks the exit code.
194- **Before every push of the corpus branch, run the full suite and compare
195 the broken list to the previous run.** It must not grow, and no case may
196 break that was holding before. The list being non-empty is expected while
197 the query engine still has open defects.
198
199## Authoring fixtures
200
201- **Epoch**: all points anchor at `fixture.T0`. For `update_every > 1`,
202 pre-align the series: `base := fixture.T0 - fixture.T0%int64(ue)` —
203 storage keeps pushed timestamps exactly, but views re-grid onto absolute
204 `update_every` multiples, so unaligned fixtures make expectations
205 needlessly hard.
206- **Host GUIDs**: `guid(n)` builds a deterministic machine GUID. `n` MUST
207 be unique across the whole suite — hosts persist in the shared daemon for
208 the entire run, so a collision silently cross-contaminates two tests.
209 Before taking a number, `grep -n 'guid(' *_test.go` and pick an unused
210 range; ranges used by loops (e.g. soak attempts) reserve their whole
211 span.
212- **Settle discipline**: after pushing, block on `td.WaitRetention(...)`
213 before querying. Ordinary helpers keep the connection open through
214 assertions to isolate storage/query checks from teardown timing. CASE-015
215 deliberately closes immediately and guards the #23118 delivered-data
216 drain guarantee; immediate close is no longer documented as data loss.
217- **Weights fixtures**: rrdcontexts stamps retention ~1–2s after chart
218 creation; weights queries return empty until then. Settle on the
219 contexts `first_time_t` (see `weightsSettle`), not only on retention.
220- **Tolerances**: exact comparison is the default. `Chart.ValueTolerance`
221 is ONLY for quantization-probing fixtures, with the reason in a comment.
222- **Tier window alignment**: `TierWindows(gran)` keys on ABSOLUTE multiples
223 of the granularity, not on offsets from `T0`. `fixture.T0 % 60 == 20`, so
224 the first tier-1 window ends at `T0+40` and a fixture whose shape is
225 keyed on the sample index straddles two regimes per stored window. Anchor
226 tier queries at `T0+40`, and let the oracle — never the fixture's index
227 arithmetic — say what each window contains.
228- **Forcing wide-point re-delivery**: ask for a view grid FINER than the
229 stored data (`DataParamsTier(ctx, 1, after, before, buckets, ...)` with
230 `buckets` a multiple of the stored window count). Each stored point is
231 then delivered to several buckets, carrying its original start and an
232 INTERPOLATED value — so any grouping that reads `value` instead of the
233 window's own statistics answers differently per bucket. That is the only
234 way to reach the repeat path from a query, and it is how
235 CASE-023/tier-wide-point caught a constant window being judged on an
236 interpolated blend of two windows.
237
238## Adding a case
239
2401. Author the fixture (Class A first; reach for a Class B oracle only when
241 the transform requires it).
2422. Push it (`pushLiveBurst` for live bursts; paced v1 or replication where
243 the ingestion path is the thing under test), settle, query.
2443. Compute expectations in Go from the fixture definition. Never paste a
245 number you got from the engine.
2464. Add one manifest entry and `MANIFEST.md` row per independently actionable
247 semantic invariant, then register each contract at the narrowest
248 test/subtest scope that proves it. Do not put independent value, units,
249 metadata, or formatting claims behind one green/red verdict.
2505. Run the full suite; a new case MUST NOT destabilize existing cases
251 (watch for GUID collisions and shared-host mutations).
252
253## Adding a case for a bug (bug workflow)
254
255A case for a known bug is written exactly like any other case: it states
256the CORRECT behavior and fails while the engine gets it wrong. It is not
257marked, excused, or inverted — it joins the broken list until the fix
258lands, and the broken list is what the corpus is for.
259
2601. Reproduce the divergence deterministically in its own `caseNNN_test.go`
261 with a minimal fixture. The check asserts the CORRECT behavior and feeds
262 the result into `assertContract`.
2632. Add the manifest entry with a `Proves` sentence stating the contract
264 precisely (what correct is), not the bug's symptoms.
2653. Confirm it fails on today's daemon, and that the failure names the real
266 defect — a case that fails for the wrong reason is worse than none.
2674. The fix goes in its OWN branch/PR — never mixed into the corpus branch.
2685. Validate the fix branch against the corpus before opening the PR:
269 - build the fix branch, save the binary aside;
270 - from the corpus checkout:
271 `QUERY_CORPUS_NETDATA=<fix-checkout>/build/netdata
272 QUERY_CORPUS_SRC=<fix-checkout>/src go test -count=1 -run '<the case
273 plus neighboring pins>' .`
274 Both paths MUST describe the same operator-declared checkout.
275 - the case MUST now hold, and every other case that was holding MUST
276 still hold (zero collateral).
2776. When the fix merges: rebase the corpus branch onto the merge, record
278 `FixedBy: "#PR"`, reword the case comment and `Proves` to describe the
279 contract in force, run the full suite, push. The case lives on as the
280 regression guard.
2817. If the divergence is ruled intended behavior instead: change the case to
282 assert the ruled behavior, document the quirk in the oracle comment and
283 the `Proves` text, and record the ruling.
284
285## Changing oracles, pins, and the harness
286
287- An oracle change MUST cite its justification: the fixture math (Class A)
288 or the C source being ported (Class B). "It makes the suite pass" is not
289 a justification — that is fit-to-engine.
290- A Class B port correction that changes expected values MUST state which
291 divergence prompted it and why it is not an engine bug.
292- Byte-pins change only with a deliberate output-contract change.
293- Determinism: expectations MUST NOT depend on wall-clock time. Tests that
294 must touch "now" (live edge, relative windows) assert ENVELOPES (bounded
295 ranges, row-count bounds), not exact values.
296- Protocol emitters (`stream/`) mirror the parser's actual grammar. At
297 `netdata/netdata @ 043f50ec075441010c1495250871d37a8ac69f8d`, the
298 authoritative surfaces are:
299 - quote/token splitting:
300 `src/libnetdata/line_splitter/line_splitter.h:34-119`;
301 - `BEGIN2`: `src/plugins.d/pluginsd_parser.c:815-960`;
302 - `SET2`: `src/plugins.d/pluginsd_parser.c:963-1125`;
303 - `END2`: `src/plugins.d/pluginsd_parser.c:1128-1163`;
304 - A/R/E flags: `src/plugins.d/pluginsd_internals.h:461-485`;
305 - `RBEGIN`/`RSET`/`REND`:
306 `src/plugins.d/pluginsd_replication.c:112-215,217-280,367-441`.
307 Extend emitters only from the relevant parser path and update the checked
308 revision/ranges when the port changes.
309
310## Known boundaries (extension points, not history)
311
312Deliberately out of scope so far; extending into them is welcome and each
313states what it takes:
314
315- **KS2 exact tail values**: the ks2 weights oracle pins the engine's
316 special cases; a full KSfbar port would make every ks2 weight exact.
317- **Natural-points full oracle**: natural mode pins count/values and a
318 two-candidate boundary check; a full oracle needs the natural-mode point
319 walk ported.
320- **64-bit counter wrap**: unreachable through the signed text protocol;
321 needs a different ingestion vector.
322- **Float collected values on the reset path**: the v1 SET path parses
323 integers; the reset/overflow pins use integer counters only.
324- **`points` > 86400**: the API caps points; oversized-grid behavior is
325 unpinned.
326- **Cloud tier**: replaying the raw halves of L5/L6 through the real cloud
327 aggregator is designed but lives outside this repo.
328
329## Gotchas
330
331- Run `go` commands from `tests/query-corpus/` (own module); running from
332 the repo root fails with "go.mod not found".
333- Values print through the engine's number formatter: a stored
334 `22.000000000000004` prints as `22`. Compare parsed numbers, not
335 strings, unless the check IS a byte-pin.
336- The default shared daemon serves most tests. Tests that need isolation,
337 rotation, or restarts boot dedicated daemons; never restart or reconfigure
338 the default shared daemon from an unrelated test.
339- After system library upgrades, rebuild `build/netdata` before blaming a
340 test failure on the suite.
341- IDE diagnostics on the Go files can be stale; `go vet ./...` is the
342 authority.