feat: rule playground (ding test-rule + ding run --dry-run)#14
Merged
Conversation
Pulls the per-alert dispatch logic out of run.go's free functions and into a NotifierDispatcher behind a small Dispatcher interface. Behavior is identical; existing tests pass unchanged after a one-line construction update. Sets up upcoming `ding test-rule` and `ding run --dry-run` to swap the real dispatcher for a logging one without further engine or ingest changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renders evaluator.Alert as human-readable multi-line text with optional ANSI color, used by upcoming `ding test-rule` and `ding run --dry-run` TTY output paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two follow-ups from code review on c864be4: - Apply ansiDim to the "message:" label in the color path so the third line is consistent with the dim treatment on the metric/value line. Message body stays unstyled (it's user-rendered template content, not formatter chrome). - Color test now asserts ansiReset is present in the output, catching any future change that opens color codes without closing them (which would leave the terminal styled until the next prompt). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JSONL output (one JSON object per line, newline-terminated). Stable schema with aggregate fields always present (zero when non-windowed) so jq pipelines don't have to nil-check. Used for --format json and the piped-stdout default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three follow-ups from code review on de751ab: - TestJSONFormatter_RoundTrips wantKeys now includes max/min/count/sum alongside avg, locking the "aggregates always present" invariant in the test (not just the comment). - TestJSONFormatter_NilNotifiers verifies the nil-coercion path: when Alert.Notifiers is nil, the JSON output renders alerts as [] not null. The round-trip test only exercised the populated case. - Code comment above json.Marshal call documents why the discarded error is safe (envelope contains only stdlib-marshalable types). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sends Satisfies cli.Dispatcher implicitly via Go duck-typing — the dryrun package does not import the cli package. Both upcoming surfaces (test-rule, run --dry-run) construct one of these instead of a NotifierDispatcher. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New subcommand that loads a ding.yaml, reads JSONL events from stdin or a positional file, and routes engine alerts through a LoggingDispatcher instead of real notifiers. Synthetic timestamps for events that omit the timestamp field; end-of-run rules fire after the last event. Format auto-detects: text on TTY, JSON when stdout is piped. --format text|json overrides; --no-color disables ANSI. Extends timestamp parsing to accept RFC3339 strings (via resolveTimestamp) in addition to the Unix epoch floats that ParseJSONLine already handles. Fixture YAML uses correct map-keyed notifiers and alert target format. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three follow-ups from code review on 3b80c19: - resolveTimestamp now returns (time.Time, bool); the loop in runTestRule branches on the boolean rather than on ev.At.IsZero(), which was always false because ingester.ParseJSONLine populates At with time.Now() unconditionally. Effect: events without a timestamp field now actually get the synthesized 1s-apart timestamps the docs promise, fixing windowed-rule authoring without explicit timestamps. - runTestRule rejects --format values other than auto/text/json with a descriptive error, instead of silently falling through to auto. - Three new tests: synthesized-timestamp path; invalid --format rejected; malformed JSON line is logged + skipped + subsequent valid line still fires. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds --dry-run, --format, and --no-color flags to `ding run`. When --dry-run is set, notifier construction still happens (for config validation) but the dispatch path is swapped for a LoggingDispatcher that writes formatted alerts to stderr. All other run-flow behavior (subprocess wrap, runctx, end-of-run rules, exit code propagation) is unchanged. Stdout stays reserved for the wrapped command's mirrored output; preview alerts and operational logs both go to stderr to avoid contaminating the wrapped command's stdout pipeline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two follow-ups from code review on aee0184: - Move dry-run --format validation to the very top of runRun so a --format typo doesn't pay the BuildFromConfig + notifier-worker startup cost before being rejected. Practically minor (CLI exits immediately) but the ordering is correct as-is. - drainOnce now skips drainNotifiers + alertLogger.Close in dry-run mode. No alerts were ever queued (LoggingDispatcher doesn't call Send), so the drain pays the full drain_timeout to flush empty queues for nothing. - --format flag description now says "when --dry-run is set" so users don't expect it to affect non-dry-run output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New "Testing rules without a workload" section in configuration.md covering both surfaces. README quickstart pointer; ding.yaml.example comment block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two follow-ups from review on d2584e4: - timestamp field description now mentions both RFC3339 strings and Unix epoch numbers (test-rule's resolveTimestamp accepts both; earlier text omitted Unix epoch). - "JSONL when piped" → "JSON (one object per line) when piped" to avoid confusion with --format json (which is the actual flag value). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three follow-ups from final cross-branch review on 53b39b0: - docs/configuration.md: the `--dry-run --format json | jq` example was broken because dry-run preview goes to stderr, not stdout. Added `2>&1` so the redirect actually pipes preview into jq. - docs/configuration.md: corrected the "uncontaminated" claim. Child stderr IS mixed with preview lines (ingestStream mirrors stderr + LoggingDispatcher writes to stderr); only stdout stays clean. - internal/cli/test_rule.go: format validation now runs before BuildFromConfig, matching the pattern fixed for runRun in 711e846. Same code-smell rationale: don't pay the config-load cost just to reject a typo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two new preview surfaces so users can verify
ding.yamlrules without running a real workload.ding test-rule— replay JSONL events through the rule engine; matching rules render their messages but no notifier sends happen. Stdin or file input; per-event, windowed (avg(value) over 5m > X), andmode: end-of-runrules all supported. Synthetic 1s-apart timestamps when events omit atimestampfield.ding run --dry-run— wraps a real command but suppresses notifier sends; runctx detection, end-of-run rules, syntheticrun.exit, and exit-code propagation are all unchanged. Preview output goes to stderr so the wrapped command's stdout stays clean.Dispatcherinterface refactor (internal/cli/dispatcher.go) lets both surfaces swapNotifierDispatcherfor aLoggingDispatcher(internal/dryrun/). Format auto-detects: text on TTY, JSON when piped;--format text|jsonand--no-coloroverrides on both surfaces.This branch also bundles the prior CircleCI-removal commit (
549fe6e) — see the rationale in that commit's message.Item 1 of 4 in an internal inward-polish roadmap (test-bench → run-lifetime windowing → recipe sweep → template helpers).
Test plan
go test ./...green (all 13 packages)go test -race ./internal/cli/... ./internal/dryrun/...cleanding test-rule events.jsonl(text on TTY, JSON when piped);ding run --dry-run -- failing-script.sh(exit code propagates, alerts to stderr, zero notifier sends)internal/cli/run_test.go::TestIngestStream_DryRun_NoNotifierSendsis the canonical guarantee🤖 Generated with Claude Code