Validates a chaos experiment's steady-state hypothesis before execution: checks that each probe metric is measurable and observable, that a recent baseline exists, that tolerances are numerically meaningful and SLI-backed, that the measurement window is defined, and that the chosen metrics would actually move under the target failure mode. Use when a chaos experiment has been authored (via chaos-experiment-author) and the team needs a pre-flight verdict before running the drill in any environment.
70
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
A chaos experiment's steady-state hypothesis is the contract that determines whether an experiment is scientifically useful or a no-op. Per principlesofchaos.org Principle 1:
"Focus on the measurable output of a system, rather than internal attributes of the system. Measurements of that output over a short period of time constitute a proxy for the system's steady state."
A hypothesis that cannot be measured, has no baseline, or would not move under the injected fault produces a verdict that means nothing. This skill runs five pre-flight checks against the hypothesis block before any tooling executes, catching bad hypotheses while the cost of fixing them is low.
The steady-state-hypothesis object requires a title and one or more probes,
each with a provider and a tolerance gate; if a probe's return value fails
its tolerance before the method, the experiment bails before running. The full
field list, the eight supported tolerance forms (scalar, boolean, string, range,
membership, regex, JSONPath, range object), and the pre-/post-method evaluation
flow are in
references/chaostoolkit-tolerance.md, per
chaostoolkit.org/reference/api/experiment/ and
chaostoolkit.org/reference/concepts/.
The probe must query a real data source the team can access right now: a Prometheus query endpoint, a Datadog API, an HTTP health endpoint, a process exit code. The metric must already be instrumented.
Fail signals:
provider points to a dashboard URL rather than an API endpoint, or
the only way to evaluate the metric is to read it manually.Pass signal: The team can run the probe in isolation right now and get a numeric or boolean return value.
Per principlesofchaos.org: "Measurements of that output over a short period of time constitute a proxy for the system's steady state." The tolerance must be anchored to observed behavior, not a guess.
Fail signals:
>= 99% when the service has never been measured).Pass signal: The team can cite a dashboard, runbook, or monitoring record showing the metric's typical value over the past 7-30 days in normal production or staging traffic.
The tolerance bounds must reflect a real service-level indicator (SLI), not an arbitrary threshold that would never be breached even during a real incident.
Fail signals:
>= 0%).Pass signal: The threshold maps to a published SLO, an error budget line, or a documented user-impact threshold (e.g., checkout completion >= 95% because below that the on-call alert fires).
Diagnostic questions:
A probe without a defined measurement window can return a point-in-time
value that is unrepresentative of system behavior. The measured_over or
equivalent window annotation in the experiment YAML must be present.
Fail signals:
measured_over: 0 or omits it entirely.Pass signal: The probe measures an aggregated value over a window of at
least 1 minute (longer for low-traffic services). For Prometheus: a
rate() or avg_over_time() expression with an explicit range vector.
For Datadog: a rollup with a defined time window.
# Acceptable: aggregated over a window
probes:
- name: checkout-completion-rate
type: probe
provider:
type: http
url: "https://metrics.internal/query?expr=avg_over_time(checkout_success_rate[5m])"
tolerance:
type: range
range: [95.0, 100.0]# Risky: single-sample point-in-time check
probes:
- name: homepage-status
type: probe
provider:
type: http
url: "https://app.example.com/"
tolerance: 200The most important check: would the injected fault actually cause this metric to change? A probe that is decoupled from the fault being injected produces a vacuous result.
Fail signals:
Pass signal: The team can trace the fault's propagation path from injection point to the metric's data source and confirm at least one step in that path directly affects the metric.
Diagnostic questions:
Experiment: inject 500ms network latency on the payment-service pod; hypothesis is that checkout completion rate stays >= 95%.
steady-state-hypothesis:
title: "Checkout completion rate stays above 95% under payment-service latency"
probes:
- name: checkout-completion-rate
type: probe
provider:
type: http
url: "https://metrics.internal/query?expr=avg_over_time(checkout_success_rate[5m])"
timeout: 10
tolerance:
type: range
range: [95.0, 100.0]Pre-flight verdict against each check:
| Check | Result | Evidence |
|---|---|---|
| 1. Measurable | Pass | HTTP probe queries Prometheus; team ran it manually and got 97.2 |
| 2. Baseline exists | Pass | Datadog dashboard shows 7-day avg of 97.1%; last deploy 3 days ago |
| 3. SLI-backed tolerance | Pass | SLO doc sets user-impact floor at 95%; on-call alert fires at 94% |
| 4. Window defined | Pass | avg_over_time([5m]) range vector; 5m is above the 1m floor |
| 5. Metric moves | Pass | Payment-service is on the critical checkout path; latency raises p95 and increases timeouts that cause checkout failures |
Verdict: hypothesis is sound. Proceed to experiment execution.
These patterns block execution outright - a Hard-reject triggered: yes in the
output below. Each maps to the check that catches it, and each subsumes the
soft anti-patterns that share its cause; do not proceed until resolved.
| Hard reject | Maps to | Why it is fatal |
|---|---|---|
| Probe returns a constant (e.g. an LB liveness check, or a single HTTP 200, that passes even when all backends are down) | Checks 1, 4, 5 | The probe cannot register degradation, so a "held" verdict is vacuous |
Boolean tolerance: true whose only false path is total unavailability | Check 3 | Tests catastrophe, not resilience |
| No baseline measurement cited in the experiment or runbook | Check 2 | The tolerance was chosen without measurement |
| Metric is an internal attribute (thread-pool queue depth, JVM heap used) that is not also a published SLI | Checks 1, 3 | Per principlesofchaos.org Principle 1, internal state is not a valid steady-state output |
| Fault and probe share no call-graph path, or a global aggregate masks a regional fault | Check 5 | A "held" result means the metric is unrelated to the fault, not that the system is resilient |
Emit one row per probe in the hypothesis block, then a summary verdict:
Steady-State Hypothesis Pre-Flight Report
==========================================
Experiment: <title>
Fault: <fault description>
Probe: <probe name>
Check 1 (measurable): PASS / FAIL - <reason>
Check 2 (baseline): PASS / FAIL - <reason>
Check 3 (SLI-backed): PASS / FAIL - <reason>
Check 4 (window): PASS / FAIL - <reason>
Check 5 (moves): PASS / FAIL - <reason>
Verdict: SOUND / UNSOUND
Blocking issues: <list or "none">
Hard-reject triggered: yes / no
Recommended action: <proceed | revise probe | replace metric | add baseline>steady-state-hypothesis
block specification: required fields (title, probes), tolerance types
(scalar, range, regex, jsonpath, probe), and evaluation semantics.chaos-experiment-author - upstream
skill that authors the experiment (Step 1 defines the hypothesis this skill
validates).prod-canary-validator (in the qa-shift-right plugin) -
provides the production steady-state metrics that can anchor hypothesis
baselines (Check 2).