chore: implement phase 1 - #2779
Conversation
|
b406d5a to
d4124cb
Compare
0e21a48 to
81ce1e0
Compare
| } | ||
|
|
||
| var forStr string | ||
| if err := opt(m, "for", &forStr); err != nil { |
There was a problem hiding this comment.
it is optional, because if for is missing it means it is equal to 0 in Grafana, which means that alert fires immediately.
d4124cb to
96000d4
Compare
81ce1e0 to
723fa13
Compare
96000d4 to
730e632
Compare
1054200 to
ac5010d
Compare
730e632 to
ca6eed4
Compare
There was a problem hiding this comment.
🟡 Changes recommended
There are correctness issues in the new parsing utilities (potential instanceKey collisions and silent empty titles for malformed datasource-managed rules) plus a misleading required-field error message that should be fixed before merge.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Implements Phase 1 of grafana-alertcheck parsing by adding strict JSON decoders for the Grafana state endpoint and ruler endpoint, plus a Prometheus-style duration parser, backed by sanitized real-world fixtures and targeted regression tests.
Changes:
- Added strict state-endpoint parsing (
ParseState) with canonicalized instance states (including composite “Normal (NoData)/(Error)” reasons) and explicit must-error coverage. - Added ruler-endpoint parsing (
ParseDefinitions) that classifies rules by shape (Grafana-managed vs datasource-managed vs recording) and parsesfor:using a Prometheus-style duration grammar. - Added fixture corpus (sanitized slices from Grafana 13.1.0 payloads + derived edge cases) and comprehensive unit tests for parsing behavior.
File summaries
| File | Description |
|---|---|
| grafana-alertcheck/internal/gate/testdata/state_zerotime_unpaused.json | Adds must-error fixture for zero-time lastEvaluation when isPaused=false. |
| grafana-alertcheck/internal/gate/testdata/state_unknown_state.json | Adds must-error fixture for syntactically-valid composite with unknown base state. |
| grafana-alertcheck/internal/gate/testdata/state_reason_composite.json | Adds fixture covering composite “Normal (NoData)” / “Normal (Error)” reasons. |
| grafana-alertcheck/internal/gate/testdata/state_paused.json | Adds fixture for paused rule behavior and zero-time lastEvaluation. |
| grafana-alertcheck/internal/gate/testdata/state_only_active_instances.json | Adds fixture reproducing “alerts trimmed but totals unchanged” shape for later-phase validation. |
| grafana-alertcheck/internal/gate/testdata/state_one_instance.json | Adds baseline “happy path” state fixture used by tests and synthesizer. |
| grafana-alertcheck/internal/gate/testdata/state_missing_state.json | Adds must-error fixture for missing required rule-level state. |
| grafana-alertcheck/internal/gate/testdata/state_missing_optional.json | Adds fixture verifying optional fields can be absent without error. |
| grafana-alertcheck/internal/gate/testdata/state_missing_name.json | Adds must-error fixture for missing required group-level name. |
| grafana-alertcheck/internal/gate/testdata/state_missing_lasteval.json | Adds must-error fixture for missing required rule-level lastEvaluation. |
| grafana-alertcheck/internal/gate/testdata/state_missing_interval.json | Adds must-error fixture for missing required group-level interval. |
| grafana-alertcheck/internal/gate/testdata/state_missing_health.json | Adds must-error fixture for missing required rule-level health. |
| grafana-alertcheck/internal/gate/testdata/state_missing_file.json | Adds must-error fixture for missing required group-level file. |
| grafana-alertcheck/internal/gate/testdata/state_health_nodata.json | Adds happy-path fixture for health=nodata and NoData instance state. |
| grafana-alertcheck/internal/gate/testdata/state_health_error.json | Adds happy-path fixture for health=error including lastError. |
| grafana-alertcheck/internal/gate/testdata/ruler_rules.json | Adds ruler fixture covering collisions, paused rules, and for: variants (incl. derived 1w). |
| grafana-alertcheck/internal/gate/testdata/ruler_recording.json | Adds derived recording-rule shape fixture to ensure parsing doesn’t require alert-only fields. |
| grafana-alertcheck/internal/gate/testdata/ruler_datasource_managed.json | Adds derived datasource-managed rule fixture (no grafana_alert) for shape classification. |
| grafana-alertcheck/internal/gate/testdata/README.md | Documents fixture provenance, sanitization rules, and what each fixture is validating. |
| grafana-alertcheck/internal/gate/parse_state.go | Implements strict parsing for the state endpoint and instance-state normalization. |
| grafana-alertcheck/internal/gate/parse_state_test.go | Adds happy-path + must-error tests, plus high-cardinality synthetic test generation. |
| grafana-alertcheck/internal/gate/parse_ruler.go | Implements strict parsing for the ruler endpoint and shape-based rule classification. |
| grafana-alertcheck/internal/gate/parse_ruler_test.go | Adds tests for ruler parsing across Grafana-managed, datasource-managed, and recording shapes. |
| grafana-alertcheck/internal/gate/jsonreq.go | Adds req/opt helpers to enforce required fields and avoid fail-open zero values. |
| grafana-alertcheck/internal/gate/jsonreq_test.go | Adds tests for required vs optional field decoding (including explicit JSON null behavior). |
| grafana-alertcheck/internal/gate/duration.go | Adds Prometheus-style duration parser supporting d, w, y, and strict unit ordering. |
| grafana-alertcheck/internal/gate/duration_test.go | Adds duration parsing tests (valid inputs + must-error cases including overflow). |
Review details
- Files reviewed: 27/27 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Strict parsers for the state and ruler endpoints (H1), a Prometheus-style duration parser, and fixtures sliced from real Grafana 13.1.0 payloads covering every required/optional-field and must-error case, including the "Normal (NoData)"/"Normal (Error)" composite reason states found live in the current fleet capture (not in the original plan's vocabulary).
ca6eed4 to
afcf257
Compare
ac5010d to
2945092
Compare
* chore: implement phase 2 Fix retry-error conflation, measure full poll latency, and harden Source test doubles for concurrency. * chore: enhance unit tests * chore: address code review comments * chore: implement phase 3 (#2781) * chore: implement phase 3 Add Resolve() for alert name resolution (uid:/Title/Folder/Title/ Folder/Group/Title forms, UID collapse, no-match suggestions) and the grafana-alertcheck CLI's list subcommand, the first runnable piece of the gate. Incorporates review fixes: reject empty path segments in classifyForm, guard uid: against an empty suffix, scope the no-match rule count and suggestions to supported rule kinds only, and exit 0 on -h/--help. * chore: enhance unit tests * chore: implement phase 4 (#2782) * chore: implement phase 4 Add per-rule poll timings, scheduler, and budget check (P4). - schedule.go: DeriveTimings, Scheduler, CheckBudget (§5) - Address review: add Folder/Title resolve test, rename CheckBudget's minPollEvery to tightestUID * chore: enhance unit tests * chore: address code review comments * chore: implement phase 5 (#2783) * chore: implement phase 5 Add the JSONL evidence log (P5). - log.go: Header/Poll records, reduction, H2 transition markers, §3.2 verification, append-only Writer with flock, ReadLog - flock_unix.go: non-blocking exclusive lock, unix only - schedule.go: DeriveTimingsFromLog — log-mode cadence comes from the header, never from the definitions * chore: remove unix build tags * chore: address code review comments * chore: implement phase 6 (#2784) * chore: implement phase 6 Invariant defended: H2. The one question: can watch return success over a window that nothing is recording? Watch() records the first observation of each non-skipped rule, then detaches a child that polls at the cadence in the header. The parent returns only after the child reports ready on an inherited pipe, and writes the pidfile after that. A clean stop writes the sentinel; a hard error does not. * chore: add a unit test, remove build tags * chore: address code review comments * chore: implement phase 7 (#2785) * chore: implement phase 7 Invariant defended: H3. The one question: can a rule be called alive because it looked alive one poll ago? proveCoverage (grafana-alertcheck/internal/gate/coverage.go) is the pure coverage function: nine checks over one rule's polls — sentinel, from-bounds, heartbeat continuity, health error/nodata, liveness, in-window pause, rule absence, KeepLast. Liveness is absolute, never a delta. Cross-domain comparisons translate by each poll's own skew and widen boundary segments by its skew bound, fail-closed. * chore: enhance unit tests * chore: address code review comments * chore: implement phase 8 (#2786) * chore: implement phase 8 Invariant defended: H6/H7. The one question: can a violation ever outrank an unobservable rule, or can a pass happen without Violations empty and err nil? Adds classify.go: the pure per-instance classifier (outcome table, preexisting policy, BadFor) and decide(), the seam combining proveCoverage with those timelines under one Policy. Consolidates rule-poll filtering and skew translation onto pollsForRule/runnerTime, shared with coverage.go. * chore: rename some vars + add unit tests * chore: address code review comments * chore: implement phase 9 (#2787) * chore: implement phase 9 Invariant defended: H5/H7. The one question: can check report a pass over a window it did not prove? Check() is the I/O shell around the pure decide(). Single-step synthesizes the header and its own sentinel, so no mode flag reaches the pure layer. Log mode stops the recorder before the one full read. The header, not a definition re-resolved after the window closed, is the authority for what was paused when the window opened — it decides `skipped`, the drain set, and the transitionGrace max. The flock, not the pidfile, is the authority for whether a writer still exists. * chore: remove unix build tag * chore: implement phase 10 (#2788) * Wire watch/check subcommands to the gate library, with a table+JSON renderer and H6/H7 exit-code mapping. Extend Result with per-rule/global thresholds and a real skew bound; export SkewHardLimit; reject --states normal. * chore: fix goreleaser.yaml and add version command * chore: implement phase 11 (#2789) * chore: implement phase 11 Add coverage.go's declared-KeepLast check (no_data_state/exec_err_state, not just an observed reason) and close the remaining §22 gaps: newly_bad's no-early-exit clock assertion, a recorder-mode gap right after the deploy, a rule's own coverage gap overriding its own recovery, a genuinely skew-discriminating staleness test, exit-2 consequences on two Reason-only coverage tests, and end-to-end checks for log-name collapse, a truncated log, and the real watch-written state histogram. * chore: address code review comments * chore: more concise comments (#2790) * chore: more concise comments * fix: merge conflict * chore: shorten comments * fix: resolve conflict * chore: use testify's require in tests (#2792) * chore: use testify's require in tests * chore: move remaining assumptions to testify * chore: address code review comments * chore: fix logging and std out printing (#2798) * chore: fix logging and std out printing * chore: truncate to seconds when comparing from time * chore: add centralized docs (#2802) * chore: add centralized docs * chore: further update docs * chore: address code review comments (#2807) * chore: address code review comments * chore: get rid of goreleaser
Strict parsers for the Alertmanager state (H1) and ruler endpoints, a Prometheus-style duration parser, and fixtures sliced from live Grafana 13.1.0 payloads covering required, optional, and must-error cases — including the "Normal (NoData)"/"Normal (Error)" composite reason states seen in production (not in the original plan).
Review focus:
parse_state.go,parse_ruler.go(strictness/error cases), andduration.go.